C语言使用指针时常犯的一些错误
1、使用为初始化的指针或者使用NULL指针
#include<stdio.h> /** * C语言使用指针常犯的错误---指针未初始化 * 使用未初始化的指针或者指向NULL等系统内存地址的指针 * 错误原因: * 1、定义一个指针未初始化时,系统将为该指针随机分配一个地址,此时若该地址指向系统内存,在该内存写入数据将会导致程序甚至系统崩溃-->使用野指针 * 2、一般来所NULL(系统地址为0x0(表示十六进制的零)所指向的地址是不允许写入数据的,强行写入会导致1一样的错误,也可以把NULL视为系统内存 * @return */ int main() { //案例1 // int * p; //案例2 int *p = NULL; *p = 10; printf("%d\n", *p); return 0; }
2、使用指针变量开辟空间后未释放内存
#include<stdio.h> #include <cstdlib> /** * C语言使用指针常犯的错误---忘记释放内存 * 1、使用指针开辟了内存后,不释放内存-->内存泄漏 * 2、释放内存后仍然使用指针(未将指针指向NULL)-->使用野指针 * 3、令指针指向NULL后继续使用该指针-->访问系统内存,系统或程序崩溃 * @return */ int main() { int *p = (int *) malloc(sizeof(int) * 100); for (int i = 0; i < 100; ++i) { p[i] = i; } for (int j = 0; j < 100; ++j) { printf("%d\n", p[j]); } //输出 //1 //.. //99 free(p); printf("%d\n", p[0]); //输出:释放了内存后,p指向的存储单元被收回,存储数据未知 //10187872 p = NULL; printf("%d\n", p[0]); //报错:使用空指针 //Process finished with exit code -1073741819 (0xC0000005) return 0; }
3、使用指针变量开辟内存后,在操作指针变量时改变了指针变量的指向,然后释放内存
#include<stdio.h> #include <cstdlib> #include <cstring> /** * C语言使用指针常犯的错误---不断修改指针变量的指向,然后进行释放内存操作 * 使用指针开辟内存后,进行操作,操作完成过程中改变了指针的指向,然后释放内存,会释放不应该释放的内存, * 而程序对部分本该释放而没有释放的内存单元失去控制,造成内存泄漏 * 解决办法:在开辟内存后,定义额外一个指针指向开辟内存的基址,该指针仅用于释放操作,而开辟内存的指针变量仅用于释放内存 * @return */ int main() { //开辟内存 char *p = (char *)malloc(sizeof(char)*100); //使用指针 strcpy(p,"abcdef"); printf("%s\n",p); //输出:abcdef //修改指针指向 p +=1; //此时p指向存储了b的存储单元 printf("%s\n",p); //输出:bcdef //释放内存 //程序不正常结束:报错 //SIGTRAP (Trace/breakpoint trap) free(p); return 0; }
4、使用野指针
#include<stdio.h> #include <cstdlib> /** * C语言使用指针常犯的错误--使用野指针 * 1、定义指针变量时未初始化,该指针不会自动指向NULL,其缺省值是随机的,该指针即变为野指针 * 2、释放内存后未将指针指向NULL,该指针变为野指针 * 野指针特点: * 用户一般很少使用空指针,应为可以使用if判断来防止使用了控制,但是野指针无法通过if判断来判别,很容易使程序崩溃 * @return */ int main() { //定义指向double变量的指针,未初始化,p变为野指针 double *p; //开辟内存 char *s = (char *)malloc(sizeof(char)*100); //...一系列操作 //释放内存 free(s); //未使s指向NULL,s变为野指针 //使用野指 if(p)printf("%lf\n", *p); //输出 //0.000000 if(s)printf("%s\n",s); //输出 //`t //指向NULL s = nullptr; if(s)printf("%s\n",s); //空指针,不执行语句 return 0; }
5、错误使用指针参数申请动态内存
/** * C语言使用指针常犯的错误---使用指针参数开辟内存 * 不要指望使用指针参数去申请动态内存, * @return */ int main() { char *p = nullptr; //p:NULL(0x0) getMemory(p, 200); //p:NULL(0x0) if (!p)printf("this is a flag\n"); //输出: //this is a flag strcpy(p, "use dynomic memory"); //SIGSEGV (Segmentation fault) printf("%s\n", p); free(p); p = NULL; //程序异常结束 //Process finished with exit code -1073741819 (0xC0000005) return 0; } /** * 使用指针参数申请内存 * 结果:在getMemory函数内,编译器为每一个参数制作一个副本,指针p的副本为_p, 编译器自动执行效果如下的语句: * _p = p; * 这里_p是一个指向char类型的指针变量,其地址指向的内存单元可供用户使用,该内存单元用来存储一个指向char类型的地址 * 而_p = p;则是意味着使_p的地址指向的内存单元存储指针p指向的char类型的地址 * 在函数内使用指针_p使用new或malloc()函数开辟内存后,变量_p的地址指向的内存单元存储的地址不在是指针变量p地址指向的存储单元存储的地址 * 在main案例中指针变量p存储的地址是NULL,_p在使用malloc()函数前存储的地址是NULL,使用malloc()函数后存储的地址由系统分配,不再是NULL * 在getMemory函数执行完毕后,_p变量销毁,但是其开辟的内存未释放,造成内存泄漏,而p变量仍然存储着NULL,其未开辟内存。 * @param p * @param size * */ void getMemory(char *p, int size) { p = (char *) malloc(sizeof(char) * size); //p:0x9515a0 "\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\356\376\356\376\356\376", <incomplete sequence \356\376> }
附1:使用指针参数申请动态内存的常见做法
#include<stdio.h> #include <cstdlib> #include <cstring> char *getMemory1(char *p, int i); void getMemory2(char **p,int i); /** * 使用指针参数开辟内存的两种方式 * 1、使用函数返回值传递动态内存 * 2、使用指向指针的指针作为参数来传递动态内存 */ int main() { char *p = nullptr; //使用函数返回值传递动态内存 p = getMemory1(p, 100); //p:0xad15a0 "\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\356\376\356\376\356\376\356\376\356\376", <incomplete sequence \356\376> strcpy(p, "use dynomic memory\n"); printf("%s", p); free(p); p = nullptr; getMemory2(&p,100); strcpy(p, "use dynomic memory again\n"); printf("%s", p); free(p); p = nullptr; return 0; } char *getMemory1(char *p, int size) { //开辟内存 p = (char *) malloc(sizeof(char) * size); //p:0xad15a0 "\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\r\360\255\272\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\253\356\376\356\376\356\376\356\376\356\376", <incomplete sequence \356\376> //使用函数值传递指向堆中的内存,不能返回指向指向栈的内存? return p; } /** * 使用指向指针的指针来传递动态内存 * 首先getMemory2()函数制作一个p的拷贝_p,p变量和_p变量的地址存储的是main函数中指针变量p(以下称为p',便于区分)的地址 * 函数内的p即_p,而不是p * *p = (char *)malloc(sizeof(char)*size); * 上述语句的作用是,为指针变量p的地址指向的内存存储的地址即指针p'开辟一块内存,将p'指向这块内存单元的基址 * 在函数执行完成后,_p销毁,但是p'的指向被改变了,故动态内存得以传递 * @param p (指向(指向(char变量)的指针)的指针) * @param size */ void getMemory2(char **p, int size) { *p = (char *) malloc(sizeof(char) * size); }
6、C语言在函数内定义结构体变量,将该变量地址进行赋值
// // Created by ljw07 on 2020/7/22. // #include<stdio.h> #include<stdlib.h> /** * 定义结构体 */ typedef struct Node{ int data; struct Node * next; }Node,*LinkList; void test(LinkList *L , int j, int y){ Node *p = *L; int i = 0; for (i = 0; i < j-1; ++i) { p = p->next; } Node s; s.data = y; s.next = NULL; printf("Node s' address is %d\n", &s); p->next = &s; } /** * C使用指针常犯的错误---函数内定义结构体变量,然后将该变量的地址赋值给指针变量 * 对于函数内定义的结构体变量,每次调用该函数所使用的内存块是一致的,即使用的是同一个结构体变量 * 因此每次获取该结构体地址得到的值是相同的,当我们想把该结构体变量的地址赋值给指向结构体的指针变量时, * 会导致多次操作的不同指针结构体的指针变量指向同一块内存,常常导致函数的实际功能与预期功能不一致, * 如同上述的test(LinkList *L,int i,int y)函数实现的在单链表L的第i个位置上插入数据域为y的结点 * 最终会发现调用test()函数在L上第1,2个位置插入分别插入数据域为3,4的结点,最终只在第1位置插入数据域为4的结点, * 而该结点的指针域指向其本身,如同形成一个循环链表,经过调试发现,在test函数中p指针始终指向结构体变量s, * 而每次调用test()函数,结构体变量s的地址都是一致的,故从第二次调用test()函数开始,test函数每次仅改变结构体变量s的数据域, * 并让其的指针域指向结构体变量s本身 * @return */ int main() { LinkList L = (LinkList *)malloc(sizeof(Node)); L->next = NULL; L->data = 0; test(&L,1,3); test(&L,2,4); test(&L,3,5); printf("%d\n", L->next); printf("%d\n", L->next->next); printf("%d\n", L->next->next->next); return 0; }
运行结果:
附2:解决在函数中定义局部结构体的方法---使用结构体指针申请动态内存来定义结构体变量,即可使每次调用函数时,得到新的内存空间
// // Created by ljw07 on 2020/7/22. // // // Created by ljw07 on 2020/7/22. // #include<stdio.h> #include<stdlib.h> /** * 定义结构体 */ typedef struct Node{ int data; struct Node * next; }Node,*LinkList; void test(LinkList *L , int j, int y){ Node *p = *L; int i = 0; for (i = 0; i < j-1; ++i) { p = p->next; } Node *s = (Node *)malloc(sizeof(Node)); s->data = y; s->next = NULL; printf("Node s' address is %d\n", s); p->next = s; }
运行结果:
路漫漫其修远兮,吾将上下而求索。