指针的使用规范
在C和C++中,指针是强大的工具,但如果不小心使用,可能会引发复杂的错误。因此,了解指针的规范使用原则可以帮助编写更安全和易于维护的代码。以下是一些使用指针的规范:
1. 初始化指针
指针变量在声明后,应尽量初始化,避免使用未初始化的指针导致未定义行为。常见的初始化方式包括:
- 将指针设置为 - nullptr(C++11起)或- NULL(C语言中):
int* ptr = nullptr; // C++
int* ptr = NULL;    // C
2. 检查指针的有效性
在解引用指针前,必须检查指针是否为空,确保它指向合法的内存地址,以防止空指针异常。
if (ptr != nullptr) {
    // 安全地使用指针
}
3. 使用指针前进行内存分配
在动态内存分配时(如malloc或new),要确保分配成功后再使用指针。
int* ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
    // 分配失败处理
}
4. 避免野指针
释放指针所指向的内存后,将指针设置为nullptr或NULL,避免指针成为“野指针”:
free(ptr);
ptr = NULL;
5. 不要重复释放指针
重复释放会导致崩溃或不可预测的行为。确保在释放指针之前检查其是否为nullptr。
6. 注意指针运算
指针运算可能导致访问非法地址。尤其在数组、字符串等场景中小心进行指针偏移操作,避免越界访问。
7. 合理使用智能指针(C++)
在C++中,使用智能指针(如std::unique_ptr和std::shared_ptr)可以减少手动管理内存的负担,防止内存泄漏:
std::unique_ptr<int> ptr = std::make_unique<int>(10);
8. 避免指针混用
当同一块内存区域有多个指针指向时,容易产生悬挂指针和未定义行为,建议尽量减少多指针对同一内存的直接操作,特别是在多线程场景下。
9. 使用const修饰符
当指针所指的值不应被修改时,使用const修饰指针或指针指向的对象,提升代码的安全性和可读性。
const int* ptr;   // 指针指向的数据不可修改
int* const ptr;   // 指针本身不可修改
const int* const ptr; // 指针和数据都不可修改
10. 注意释放动态数组内存
在使用new[]分配动态数组时,应使用delete[]来释放内存,以避免未定义行为。
int* arr = new int[10];
delete[] arr;
11. 避免返回局部变量的地址
在函数中返回局部变量的地址是一个常见错误,因为函数结束后,局部变量会被销毁。
int* func() {
    int x = 10;
    return &x; // 错误
}
按照这些规范,能够更安全地管理指针的生命周期和访问权限,从而避免许多常见的指针错误。
多资源控制问题
在多资源控制问题中,尤其是多线程或多进程环境中,合理管理和控制资源以避免竞争、死锁等问题是关键。以下是一些多资源控制的规范和建议:
1. 避免死锁(Deadlock)
死锁是多个资源竞争时常见的问题。避免死锁可以通过以下方法实现:
- 固定资源请求顺序:所有线程按照相同的顺序申请资源。比如,始终先申请资源A再申请资源B。 
- 使用超时机制:设置资源获取的超时,当等待超时则释放已持有的资源。 
- 死锁检测:通过算法定期检测死锁情况,并根据情况释放部分资源。 
2. 使用互斥锁(Mutex)保护共享资源
在多个线程访问同一共享资源时使用互斥锁,避免数据竞争问题。
- 使用语言提供的互斥锁,如C++的 - std::mutex或Python的- threading.Lock。
- 尽量缩小锁的范围,仅锁定访问共享资源的代码段,减少锁的持有时间,提高效率。 
3. 采用读写锁(Read-Write Lock)
在读多写少的场景中,使用读写锁提高资源访问效率。读写锁允许多个读线程并发执行,但写线程必须独占。
- C++中可以使用 - std::shared_mutex和- std::unique_lock控制读写锁。
- 避免多个写操作同时进行,以防止竞争和资源不一致。 
4. 使用条件变量(Condition Variable)实现同步
条件变量是协调线程执行的常用工具,用于等待特定条件成立时才开始执行。
- 在C++中使用 - std::condition_variable。
- 在Python中可以使用 - threading.Condition。
- 确保条件变量的状态在适当条件下更新,避免线程长时间等待或进入无用的忙等待状态。 
5. 资源池管理
对于多线程使用频繁的资源(如数据库连接、线程、文件句柄等),可以采用资源池模式,减少创建和销毁资源的开销。
- 在C++中可以通过对象池(如使用 - std::vector管理一组资源)来实现。
- 在Python中可以使用 - multiprocessing.Pool管理多进程任务。
- 注意资源池大小设置,防止资源不足或资源闲置。 
6. 使用原子操作
对于简单计数、标志位等轻量资源的操作,优先使用原子操作,以减少锁的使用,提升性能。
- C++中可以使用 - std::atomic操作整型变量。
- Python中多线程环境下可以使用 - queue或- multiprocessing.Value控制共享状态。
- 原子操作适用于轻量、短时间占用的资源,复杂资源共享应配合互斥锁使用。 
7. 避免不必要的资源共享
尽量减少资源共享的范围,尤其是跨线程共享,限制资源的使用范围,提高系统的可维护性和安全性。
- 使用局部变量来避免不必要的共享。 
- 仅将资源共享给需要的线程或模块。 
8. 选择合适的锁类型
不同场景中可以根据需求选择适合的锁类型:
- 递归锁(Recursive Lock):允许同一线程多次申请相同的锁。适用于递归调用等情形。 
- 自旋锁(Spin Lock):适用于锁时间很短的情况,避免线程切换的开销,但不适合长时间锁定资源。 
- 互斥锁(Mutex):常用于保护较长时间的资源操作,确保线程独占访问。 
9. 定期释放资源
长时间占用资源会导致资源紧张,尤其是内存、文件句柄等,应尽量释放不再使用的资源。
- 采用RAII(Resource Acquisition Is Initialization)模式,通过对象的生命周期管理资源。 
- 定期清理资源池中的闲置资源。 
10. 检查并避免活锁(Livelock)
活锁是线程或进程不断改变状态却始终无法达成目标的一种情况。避免活锁的办法包括:
- 增加随机等待时间,减少线程间频繁相互让出资源的现象。 
- 设置一个循环检查计数器,当超出限制时强制解锁或释放资源。 
通过以上多资源控制的规范和最佳实践,能够大大减少资源竞争、死锁、活锁等问题,提高程序的效率和稳定性。
#include <stdio.h>
#include <string.h>
int main (int argc, char **argv, char *envp[]) {
void ErrorProc()
{
}
    int *pA = (int *)malloc(sizeof(int));
    if(pA == NULL)
    {
        ErrorProc();
        return -1;
    }
    *pA = 999;
    char *pB1;
    float *pB2;
    if (argc % 2 == 0)
    {
        pB1 = (char *)malloc(strlen("Hello") + sizeof(char));
        if(pB1 == NULL)
        {
            free(pA);
            ErrorProc();
            return -1;
        }
        strcpy(pB1, "Hello");
    }
    else
    {
        pB2 = (float *)malloc(sizeof(float));
        if(pB2 == NULL)
        {
            free(pA);
            ErrorProc();
            return -1;
        }
        *pB2 = 3.14f;
    }
    double *pC = (double *)malloc(sizeof(double));
    if (pC == NULL)
    {
        free(pB2);
        free(pB1);
        free(pA);  
        ErrorProc();
        return -1;
    }
    *pC = 0.618;
}
在多资源管理中,堆中我们申请了A,然后分支下去B1,B2,然后又指向C。是一个菱形。这种时候我们如果发生错误,我们应该从后向前挨个释放。
#include <stdio.h>
#include <string.h>
#define SAFE_FREE(p) if(p){free(p);(p)=NULL;}
void ErrorProc()
{
}
int main (int argc, char **argv, char *envp[])
{
  //多资源使用规范
  //1.引用资源的变量在作用域开始定义, 并初始化为错误值。
  int *pA = NULL;
  int *pB1 = NULL;
  int *pB2 = NULL;
  double *pC = NULL;
  //2.申请资源后,必须检查是否资源有效,无效则处理错误
  pA = (int *)malloc(sizeof(int));
  if (pA ==NULL)
  {
    ErrorProc();
    //3.处理完错误后,跳转到统一退出位置
    goto EXIT_PROC;
  }
  //资源有效正常使用
  *pA = 999;
 EXIT_PROC:
  SAFE_FREE(pC);
  SAFE_FREE(pB2);
  SAFE_FREE(pB1);
  SAFE_FREE(pA);
  /*
    //4.释放资源前,必须要检查资源是否有效,无效则不处理
    if (pA != NULL)
    {
      free(pA);
      //5.释放资源后,必须将引用资源的变量重置为错误值
      pA = NULL;
    }
  */
  return 0;
这个结构就是标准的多资源流程,在cpp里有try catch,goto有throw
位运算
A and 1 = A
A and 0 = 0
A or 1 = 1
A xor A = 0
A xor 0 = A
A xor 1 = $\neg$A
A and $\neg$ A= 0
A or $\neg$A =
#include <stdio.h>
int my(int n )
{
  unsigned int i = 7;
  i = i >> 1; //3
  i = -1;
  i = i >> 1; //0xffffffff >> 1 = 0x7fffffff
  int j = 7;
  j = j >> 1; //3
  i = -1;
  i = i >> 1; // 0xffffffff >> 1 = oxffffffff
  // f(0x1111) -> 7(0x111) -> 3(0x11) -> 1(0x1) -> -1(-0x1) -> -1(0x1)
  // 移位前面补充0,如果是-1前面补符号位,本身不变
}
int myabs(int n)
{
  int i = n >> 31; // if n >= 0, i = 0; else i = 0xffffffff = -1
  n = n ^ i;       // if i = 0, n = n; else i = 0xffffffff, n = ~n
  return n - i;    // if i = 0, n - i = n; else i = -1, n - i = n + 1
}
int main ()
{
  int n = myabs(-5);
  return 0;
}
myabs2 函数
这个函数通过位运算实现绝对值的计算,具体步骤如下:
- int i = n >> 31;
- 这一步利用右移运算符获取 - n的符号位。若- n是负数,- i会被赋值为- 0xFFFFFFFF(即 -1);若- n是正数或零,- i将为 0。
- n = n ^ i;
- 使用异或运算来翻转 - n的所有位。当- i为 0 时,- n不变;当- i为 -1 时,- n的所有位都被翻转,即- n变成- ~n(取反)。
- return n - i;
- 如果 - i是 0,则返回- n;如果- i是 -1,则返回- n + 1。这一步的目的是处理负数情况下的补偿。
main 函数
- int fuck = 0;:定义一个整数变量。
- scanf("%d", &fuck);:读取用户输入的整数。
- int n = myabs2(fuck);:调用- myabs2函数,计算绝对值并赋值给- n。
- printf("%d", n);:输出绝对值。
总结
myabs2 函数以高效且简洁的方式计算了整数的绝对值,利用了位运算和逻辑运算。myabs 函数则没有实现有效的功能,可能是一个未完成或错误的示例。总体来说,代码展示了如何使用位运算来实现简单的数学计算。
算数移动
左移和右移是位运算中的基本操作,以下是它们的规则和补位方式:
左移(<<)
- 操作:将二进制数的所有位向左移动指定的位数。 
- 规则: 
- 移动后,低位(右侧)用 0 填充。 
- 左移相当于乘以 2 的 n 次方(n 为移动的位数)。 
示例:
- 0000 0001(1)左移 1 位:
0000 0001 << 1  -->  0000 0010  (2)
右移(>>)
- 操作:将二进制数的所有位向右移动指定的位数。 
- 规则: 
- 算术右移(通常用于带符号整数):最高位(符号位)保持不变,填充新位为符号位的值(1 或 0)。 - 如果是负数,补位为 1;如果是非负数,补位为 0。 
 
- 逻辑右移:不考虑符号位,始终用 0 填充新位(通常用于无符号整数)。 
示例:
- 算术右移: 
- 对于 - 1111 1010(-6,补码形式)右移 1 位:
1111 1010 >> 1  -->  1111 1101  (-3)
- 逻辑右移: 
- 对于 - 1111 1010(-6,补码形式)逻辑右移 1 位:
1111 1010 >> 1  -->  0111 1101  (125)
总结
- 左移:将数的所有位向左移动,用 0 填充低位。相当于乘以 2 的 n 次方。 
- 右移: 
- 算术右移:保持符号位,符号位填充新位。 
- 逻辑右移:无论符号,均用 0 填充新位。 
不同的编程语言可能对这两种右移的实现有所不同,所以在使用时要注意具体的语言规则。
权限控制
#define ADD 1 //0001
#define DEL 2 //0010
#define EDT 4 //0100
#define QUE 8 //1000
#include <cstdio>
int main ()
{
    int nPrivilege = 0;
    nPrivilege = nPrivilege | DEL;
    nPrivilege = nPrivilege | EDT;
    if (nPrivilege & ADD)
    {
        puts("Add Privileges");
    }
    if (nPrivilege & DEL)
    {
        puts("Delete Privileges");
    }
    if (nPrivilege & EDT)
    {
        puts("Edit Privileges");
    }
    if (nPrivilege & QUE)
    {
        puts("Queue Privileges");
    }
    return 0;
}
int nPrivilege = 0;
nPrivilege = nPrivilege | DEL;
nPrivilege = nPrivilege | EDT;
在这一部分代码中,nPrivilege 先被初始化为 0,然后通过按位或操作依次添加 DEL 和 EDT 权限。由于 DEL 和 EDT 的值分别是 2(0010)和 4(0100),执行完这两次赋值操作后,nPrivilege 的值为 6(二进制表示为 0110)。
接下来的条件判断逐一检查 nPrivilege 中是否包含 ADD、DEL、EDT 和 QUE 权限:
- if (nPrivilege & ADD):不满足,因为- nPrivilege中没有- ADD权限。
- if (nPrivilege & DEL):满足,- nPrivilege中包含- DEL权限。
- if (nPrivilege & EDT):满足,- nPrivilege中包含- EDT权限。
- if (nPrivilege & QUE):不满足,因为- nPrivilege中没有- QUE权限。
因此,程序的最终输出为:
Delete Privileges
Edit Privileges
这表示当前权限 nPrivilege 中包含 DEL(删除)和 EDT(编辑)权限。
位或与位与
位或和位与是两种常见的位运算操作,主要用于处理二进制位。它们的作用分别是设置位(位或)和检测位(位与)。我们分别来看一下它们的特点和用法。
位或(|)
位或操作会将两个数的每个位进行比较,只要其中一个数的该位是 1,结果的对应位就是 1。位或主要用于设置位,即将特定的位设为 1,通常用于添加权限标志或组合多个标志。
示例
例如,将权限 ADD(0001)和 DEL(0010)组合到一起:
int nPrivilege = 0;
nPrivilege = nPrivilege | ADD;  // 设置 ADD 权限
nPrivilege = nPrivilege | DEL;  // 设置 DEL 权限
位或运算过程:
  0001   (ADD)
| 0010   (DEL)
  ----
  0011   (结果)
结果为 0011,表示当前权限包含 ADD 和 DEL。
位与(&)
位与操作会将两个数的每个位进行比较,只有两个数的该位都是 1 时,结果的对应位才是 1。位与主要用于检测位,即检查某个位是否为 1,常用于判断是否包含某个权限。
示例
假设 nPrivilege 的值为 0011(表示包含 ADD 和 DEL),我们可以用位与操作检查它是否包含 DEL 权限。
if (nPrivilege & DEL)
{
    puts("Delete Privileges");
}
位与运算过程:
  0011   (nPrivilege)
& 0010   (DEL)
  ----
  0010   (结果)
结果为 0010(非零),说明 nPrivilege 中包含 DEL 权限。
位或和位与的综合应用
在权限控制中,通常通过位或设置权限,通过位与检测权限。例如:
总结
- 位或 ( - |):用于设置位,将特定的权限添加到- nPrivilege中。
- 位与 ( - &):用于检测位,检查- nPrivilege是否包含特定的权限。
//描述位数关系,注释标准
//nnnnxxxxxxxxxxxxxxxxxxxxrrrr qeda
//a : add
//d : del
//e : edt
//q : que
//n : no use     未使用
//x : no define  未定义
//r : reserved   保留
#include <cstdio>
struct tagPrivilege
{
    //按位对应
    //31                          3210
    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    int add:1; //:1只影响一位
    int del:1;
    int edt:1;
    int que:1;
};
int main ()
{
    struct tagPrivilege pri = {0};
    pri.del = 1;
    pri.edt = 1;
    if (pri.add)
    {
        puts("add");
    }
    if (pri.del)
    {
        puts("del");
    }
    if (pri.edt)
    {
        puts("edt");
    }
    if (pri.que)
    {
        puts("que");
    }
}
这段代码定义了一个 tagPrivilege 结构体,通过位域(bit-field)来精确控制每个权限标志的占用位数。具体来说,每个权限标志 (add、del、edt、que) 都使用一个比特位(int :1)来表示是否拥有该权限,这样可以节省空间,并且易于按位访问。
代码详解与注释
#include <cstdio>
struct tagPrivilege
{
    // 按位对应
    // 31                          3210
    // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    int add:1; // :1 表示该字段占用 1 位,代表 "add" 权限
    int del:1; // :1 表示该字段占用 1 位,代表 "del" 权限
    int edt:1; // :1 表示该字段占用 1 位,代表 "edt" 权限
    int que:1; // :1 表示该字段占用 1 位,代表 "que" 权限
};
int main ()
{
    struct tagPrivilege pri = {0}; // 初始化所有位为 0(没有权限)
    pri.del = 1; // 设置 "del" 权限
    pri.edt = 1; // 设置 "edt" 权限
    // 检查各权限位并输出对应的权限
    if (pri.add)
    {
        puts("add");
    }
    if (pri.del)
    {
        puts("del");
    }
    if (pri.edt)
    {
        puts("edt");
    }
    if (pri.que)
    {
        puts("que");
    }
}
代码逻辑
- struct tagPrivilege结构体中每个字段都使用- int :1,这表示每个字段只占用一个比特位。
- pri.del = 1;和- pri.edt = 1;设置了- del和- edt权限位,其余权限保持为- 0。
- 在 - main函数中,通过条件判断每个权限位是否为- 1,输出对应的权限。
程序的输出
由于设置了 del 和 edt 权限,程序的输出将为:
del
edt
位定义说明
根据注释中的格式定义,可以对各字段的用途进行解释:
- a : add-- add表示添加权限,占用 1 位。
- d : del-- del表示删除权限,占用 1 位。
- e : edt-- edt表示编辑权限,占用 1 位。
- q : que-- que表示查询权限,占用 1 位。
- n : no use- 表示未使用的位。
- x : no define- 表示未定义的位。
- r : reserved- 表示保留位。
每个权限使用一位,按位组合后,能够节省内存并便于权限判断。
评论