c++野指针小结

1.什么是野指针

所谓野指针(wild pointer),简单讲是指指向不可用的内存区域的指针。需要注意的一点是,野指针与NULL空指针是不同的。NULL指针一般比较好判断,直接用if (p==NULL)语句判断即可。但是野指针指向的是垃圾内存区域的指针,一旦使用往往会造成不可预测的结果,这种随机不可预测的结果才是最可怕的。

2.未初始化

造成野指针最常见的情况之一就是指针未被正确初始化。任何指针在被创建的时候,不会自动变成NULL指针,他的default值是随机的。所以一个比较好的习惯是,指针刚创建的时候,要么设置为NULL空指针,要么指向合理的内存区域。

看一下一个简单例子

void func() {
    int* p;
    cout<<*p<<endl;
}

我们在main方法中调用该函数,会得到输出

1901647869

不难看出得到的就是一个随机值。

3.悬垂指针

悬垂指针也是野指针常见的一种。当我们显式地从内存中删除一个对象或者返回时通过销毁栈帧,并不会改变相关的指针的值。这个指针实际仍然指向内存中相同位置,甚至该位置仍然可以被读写,只不过这时候该内存区域完全不可控,因为你不能保证这块内存是不是被别的程序或者代码使用过。

void dangling_point() {
    char *p = (char *)malloc(10);
    strcpy(p, "abc");
    cout<<p<<endl;
    free(p);
    if (p != NULL) cout<<p<<endl;
    strcpy(p, "def"); 
}

我们执行free§语句时,p所指向的内存被释放,此时该内存区域变成不可用内存。但是,p此时指向的地址并没有发生改变,而且也不会为空,所以if(p != NULL)判断为真,会继续执行后面的语句。
但是strcpy(p, “def”); 这一句,虽然代码不会报错崩溃,但此时正在篡改动态内存区,会产生不可预料的结果,此操作相当危险,尤其是debug时候非常不好排查,往往会让人崩溃…

为了避免悬垂指针的问题,一般做法是在free/delete指针以后,再将p=NULL,这样就可以避免上述问题。

4.返回栈内存指针或引用

看一个例子

int* return_ret() {
    int result = 1;
    return &result;
}

int main(int argc, char const *argv[])
{
    int *p = return_ret();
    cout<<*p<

首先,这段代码能编译通过。不过IDE会提醒warning:

test_code.cc:80:13: warning: address of stack memory associated with local variable 'result' returned [-Wreturn-stack-address]
    return &result;
            ^~~~~~
1 warning generated.

什么意思?就是我们返回了一个局部变量的栈内内存地址。
代码也能正常运行:

1
32767

我们注意看,第一个打印语句,甚至还能输出"正确"的结果1。
但是到了第二个打印语句,此时输出的结果就不可控了,因为此时该内存区域的内容,已经发生了变化。此时该内存地址已经变得不"可靠",操作该内存区域将会相当危险。

5.特殊的空指针

空指针为一特殊指针,是唯一一个对任何指针类型都合法的指针值。为了提高程序可读性,标准库定义了一个与0等价的符号常量NULL。p = 0;p = NULL;都是把p置为空指针值,上面两种写法等价。

6.free操作的真相

以下部分内容来自网络:

内存管理有以下几个层次(从高到低):C程序 - C库(malloc)- 操作系统 - 物理内存

首先,操作系统保证每个进程都有独立的虚拟内存空间(32bit上应该是4G吧,一般进程也用不了这么多)。当然实际上物理内存是所有进程共享的,所以当你需要动态内存时,需要向操作系统申请,这时候虽然从你程序的角度,内存是连续的,其实是被操作系统映射到某一块物理内存而已。程序用完内存归还后,实际归还的部分可能被操作系统分配给其他进程。

要注意,上面说的“归还”是malloc库的行为。malloc库会使用一些策略来提高内存使用的效率,比如程序需要使用10K内存时,malloc实际可能上会申请1M,因为一次系统调用开销很大;再比如即使你调用了free“归还“了程序使用的内存,malloc库也可能并未真正把这些内存归还给操作系统,因为将来程序可能还会再申请动态内存。

malloc库有多种实现,我知道的一种是使用标记(tag)来存储内存的元信息。比如你申请了8个byte,得到的头指针地址是0x1001(实际内存为0x1001-0x1008),malloc会在0x1000(也就是头指针-1的位置)保存8,即这段内存的长度。等释放时,程序将头指针地址传给free,malloc库从头指针-1的位置发现需要释放的内存长度,释放内存(实际的操作可能只是将tag清空)。这就解释了:1. 为什么和malloc不同,free的参数只有一个头指针而不需要长度;2. free后内存实际上可能并未归还给操作系统。

所以,访问被(程序)释放的内存是一种undefined行为,就是说结果是不确定的。在malloc库未将此内存归还给操作系统也未进行下一次动态分配时,这块内存事实上仍属于程序。而当malloc库不清理归还的内存时(多数实现都是如此),你能访问到的值仍是原来的值。这和函数调用完毕而未清理栈帧、后续调用函数可以访问到之前已经设置的局部变量值是一个道理。

但是,当malloc库已经将内存归还给系统时,再去访问原来的地址(更别说写),由于这段地址已经不属于程序了,就会出现经典的segmentation fault。

说到底,这些现象还是C语言库为了更有效率的实现而妥协的结果。

你可能感兴趣的:(c/c++,野指针,悬垂指针,空指针)