【C深入】野指针和内存操作分析

初始野指针

野指针通常是因为指针变量中保存的值不是一个合法的内存地址而造成的。

合法的内存地址:

1.堆空间申请的动态空间  

2.栈空间上的局部变量所占用的空间

野指针不是NULL指针,是指不可用内存的指针

NULL指针不容易出错,因为if语句很好判断一个指针是不是NULL

C语言没有任何手段可以判断一个指针为野指针!

野指针的由来

1.局部指针没有被初始化

struct Student
{
    char* name;
    int number;
};
int main()
{
    struct Student s;    
    strcpy(s.name, "Delphi Tang"); // OOPS!   s.name没有被初始化是随机值,乱指,所以不能往这个指针随意写
    s.number = 99;   
    return 0;
}

2.使用已经释放过后的指针

void func(char* p)
{
    printf("%s\n", p);
    free(p);
}
int main()
{
    char* s = (char*)malloc(5);    
    strcpy(s, "Delphi Tang");     //申请的5个字节不够放要赋值的字符串
    func(s);   
    printf("%s\n", s); // OOPS!    func()已经释放了s
    return 0;
}

3.指针所指向的变量在指针之前被销毁

char* func()
{
    char p[] = "Delphi Tang";    //p是局部数组
    return p;    
}       //调用结束后回被释放
int main()
{
    char* s = func();   
    printf("%s\n", s); // OOPS!     
    return 0;
}

经典错误:

非法内存操作分析

1.结构体成员指针未分配

2.没有为结构体指针分配足够的内存

struct Demo
{
    int* p;
};
int main()
{
    struct Demo d1;
    struct Demo d2;
    
    int i = 0;
    for(i=0; i<10; i++)
    {
        d1.p[i] = 0; // OOPS!  d1里面的p已经是个野指针了,这是操作随机的内存块
    }
    
    d2.p = (int*)calloc(5, sizeof(int));    
    for(i=0; i<10; i++)
    {
        d2.p[i] = i; // OOPS!  只分配了5个int,循环10次,虽然d2.p不是野指针,但是改写了d2.p有权力使用的内存的后面5个字节,程序越界,有可能改写了其他变量
    }    
    free(d2.p);    
    return 0;
}

内存初始化分析

内存分配成功,但并未初始化

int main()
{
    char* s = (char*)malloc(10);  
    printf(s); // OOPS!   内存申请成功后没有初始化   
    free(s);      

    return 0;
}

内存越界分析

数组越界

void f(int a[10])
{
    int i = 0;   
    for(i=0; i<10; i++)
    {
        a[i] = i;   // OOPS!   本意只要接受包含10个元素的数组,但接收的是一个有5个元素的数组,这样就越界了
        printf("%d\n", a[i]);
    }
}
int main()
{
    int a[5];    
    f(a);      
    return 0;
}

内存泄露分析

void f(unsigned int size)
{
    int* p = (int*)malloc(size*sizeof(int));
    int i = 0;   
    if( size % 2 != 0 )
    {
        return;    // OOPS!  如果不是偶数直接返回没有执行下面代码,但是动态申请的内存没有释放归还
    }    
    for(i=0; i<size; i++)
    {
        p[i] = i;
        printf("%d\n", p[i]);
    }   
    free(p);
}    //单入口双出口,第二个出口retuen,内存没有释放

设计函数的时候,最好单入口单出口

多次指针释放

void f(int* p, int size)
{
    int i = 0;
    
    for(i=0; i<size; i++)
    {
        p[i] = i;
        printf("%d\n", p[i]);
    }
    
    free(p);   //调用完这个函数释放了一次
}


int main()
{
    int* p = (int*)malloc(5 * sizeof(int));
    
    f(p, 5);
    
    free(p); // OOPS!  
       
    return 0;
}
//习惯:谁申请谁释放,main里面申请的就在main里面释放
使用已释放的指针
void f(int* p, int size)
{
    int i = 0;  
    for(i=0; i<size; i++)
    {
        printf("%d\n", p[i]);
    }    
    free(p);
}


int main()
{
    int* p = (int*)malloc(5 * sizeof(int));
    int i = 0;
    
    f(p, 5); //调用完该函数后堆空间已经被释放了,则p变为野指针
    
    for(i=0; i<5; i++)
    {
        p[i] = i; // OOPS!
    }       
    return 0;
}

C语言中的交通规则

1.用malloc申请了内存之后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针

int* p = (int*)malloc(5 * sizeof(int));
if(p != NULL)  //必须验证内存是否分配成功,因为malloc申请有不成功的可能
{

//do something here

} 
free(p);

2.牢记数组的长度,防止数组越界操作,考虑使用柔性数组

typedef struct _soft_array

{

int  len;

int  array[];

}SoftArray;

int i = 0;

SoftArray* sa = (SoftArry*)malloc(sizeof(SoftArray) + sizeof(int) * 10);

sa->len = 10;

for(i=0; i<sa->len; i++)

{

sa->array[i] = i + 1;

}

3.动态申请的操作必须和释放操作匹配,防止内存泄露和多长释放

void f()

{

int* p = (int*)malloc(5);

free(p);

}

int main()

{

int* p = (int*)malloc(10);

f();

free(p);

return 0;

}

4.习惯:free指针之后必须立即赋值为NULL

int* p = (int*)malloc(10);

free(p);

p = NULL;

// ....

//.....

//......
if( p != NULL )
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        p[i] = i;
    }

}

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80893271