C++深入理解指针

  1. NULL表示特殊指针,((void*)0),NUL是一个char,定义为\0

  2. 资源获取即初始化 (Resource Acquisition Is Initialization, RAII)
    GNU编绎器提供了非标准的扩展来支持这个特性,通过演示如何在一个函数中分配内存然后释放可以说明这种扩展。一旦变量超出作用域会自动触发释放过程。

  3. GNU的扩展要用到RAII_VARIABLE宏,它声明一个变量,然后给变量关联如下属性:
    一个类型、创建变量时执行的函数、变量超出作用域时执行的函数。
    #define RAII_VARIABLE(vartype, varname, initval, dtor)\
    void _dtor_##varname (vartype* v) {dtor(*v);}\
    vartype varname __attribute__((cleanup(_dtor_##varname))) = (initval)
    在下例中,我们将name变量声明为字符指针。创建它时会执行malloc函数,为其分配32字节。当函数结束时,name超出作用域会执行free函数:
    void raiiExample() {
    RAII_VARIABLE(char*, name, (char*)malloc(32), free);
    strcpy(name, “RAII Example”);
    printf(“%s\n”, name);
    }

  4. 初始化静态或全局变量时不能调用malloc,下面的代码声明一个静态变量,并试图用malloc来初始化:
    static int *pi = malloc(sizeof(int));
    这样会产生一个编绎时错误消息,全局变量也一样,但是静态变量可以通过
    static int *pi;
    pi = malloc(sizeof(int));
    的赋值语句给变量分配内存避免这个问题。
    全局变量不可,因为全局变量是在函数和可执行代码外部声明的。
    在编绎器看来,作为初始化操作符的=和作为赋值操作符的=不一样。

指针和函数

  1. 要理解函数和指针的结合使用,需要理解程序栈。大部分现代的块结构语言,比如C,都用到了程序栈来支持函数执行。调用函数时,会创建函数的栈帧并将其推到程序栈上。函数返回时,其栈帧从程序栈上弹出。
    栈帧用于保存局部变量,也称为自动变量,它们总是分配在栈帧上。
    程序栈是支持函数执行的内存区域,通常和堆共享,共享同一块内存区域。程序栈通常占据这块区域的下部,而堆用的则是上部。
    栈帧由以下几种元素组成:
  • 返回地址:函数完成后要返回的程序内部地址

  • 局部数据存储:为局部变量分配的内存

  • 参数存储:为函数参数分配的内存

  • 栈指针和基指针:运行时系统用来管理栈的指针

  1. 传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

  2. static变量的作用域在函数内部,但是分配在栈帧外面,静态数组必须声明为固定长度。

  3. 返回指向局部变量的指针是错误的,原因是因为局部变量分配的内存会在后续的函数调用中被覆盖。

  4. 数组int vector[5]; vector、&vector[0]表示数组第一个元素地址,得到的是整数指针,&vector获取数组本身的地址,返回的是整个数组的指针。
    二维数组int matrix[2][5];matrix[0]、&matrix[0][0]返回数组第一行第一个元素的地址,
    int* arr = &matrix[2][5]; 指针表示法*(arr+(i*cols)+j))与数组表示法(arr+i)[j]解释为数组内部偏移量表示二维中某一行列的元素,不能用arr[i][j]表示,因为没有将指针声明为二维数组。

不规则数组

  1. 复合字面量 (const int){100}; (int[3]) {10, 20, 30}; 下面的声明把数组声明为整数指针的数组,然后用复合字面量语句块进行初始化,由此创建二维数组arr1:
    int (*(arr1[])) = { (int[]){0,1,2}, (int[]){3,4,5}, (int[]){6,7,8} };
    稍微修改就能得到不规则数组int (*(arr2[])) = {(int[]){0,1,2,4}, (int[]){4,5}, (int[]){6,7,8} };

字符串

  1. 单字节字符串:由char数据类型组成的序列

  2. 宽字节字符串:由wchar_t数据类型(宽字符16或32位宽)组成的序列
    创建宽字符主要用来支持非拉丁字符集,用于支持外语的应用程序。

  3. 初始化操作与赋值操作
    char* header1 = “Media Player”;
    char* header2;
    header2 = “Media Player”; 将字符串字面量的地址赋给字符指针
    char header3 [] = “Media Player”; 初始化操作,正确,数组长度13
    char header4[13];
    header4 = “Media Player”; 错误,数组名不能作为左值进行赋值操作
    试图用字符字面量来初始化char指针不会起作用。因为字符字面量是int 类型
    char* header = “Media Player”; 正确
    char* header = ‘M’; 错误
    字符串字面量初始化字符指针及字符数组时,字符指针指向字符串字面量池中该字符串字面量的地址,字符数组则是将该字符串字面量副本存在为字符数组分配的数据栈或堆中

  4. 类型定义与宏定义,类型定义允许编绎器检查作用域规则,宏定义不一定会。
    Typedef int* PINT;
    PINT ptr1,ptr2;
    #define int* PINT;
    PINT ptr1,ptr2;

  5. strcpy和strcat这类字符串函数稍不留神就会引发缓冲区溢出,C11中加入了strcat_s和strcpy_s函数,strcpy_s函数的使用它接受三个参数,目标缓冲区、目标缓冲区的长度和源缓冲区。如果返回值是0就表示没有错误发生。还有scanf_s和wscanf_s函数可以用来防止缓冲区溢出。

  6. 使用某些函数可能造成攻击者用格式化字符串攻击的方法访问内存,在这类攻击中,将用户提供的格式化字符串打造得可以访问内存,甚至能够注入代码。printf、fprintf、snprintf和syslog这些函数都接受格式化字符串作为参数,避免这类攻击的一种简单方法是永远不要把用户提供的格式化字符串传给这些函数。

  7. 结构体内存不同数据类型使用不同对齐机制,指针有效偏移量可能会出错,误用对齐的指针可能会导致程序非正常终止或是取到错误数据,此外,如果编绎器需要生成额外的机器码来弥补不恰当的对齐,那么指针访问也可以变慢。

  8. 如果函数和函数指针的签名不同,不要把函数赋给函数指针。

  9. 同一内存重复释放,避免这类漏洞的简单办法是释放指针后总是将其置为NULL,大部分堆管理器都会忽略后续对空指针的释放。

  10. 当你需要寻址地址0的内存时,有时候编绎器会把它当做NULL指针值。底层内核程序通常需要访问地址0,有几种技术可以处理这种情况:
    把指针置为0(不一定能工作)
    把整数置为0,再把这个整数转换为指针
    使用联合体
    用memset函数把指针置为0(memset((void*)&ptr, 0, sizeof(ptr)))

  11. 访问端口的软件一般是操作系统的一部分,
    #define PORT 0Xb0000000
    unsigned int volatile * const port = (unsigned int *) PORT;
    机器用十六进制地址表示端口,将数据作为无符号整数处理。volatile关键字修饰符表示可以在程序以外改变变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值