概述
指针是C语言的核心内容,同样也是C语言中的难点,更是C语言的一大缺陷。大部分初学者在编写与指针相关的Linux C程序时都会碰到“Segmentation Default”的错误,令人头疼而不知所措。
本篇博客总结了本人在学习Linux C的历程中遇见的所有指(nei)针(xin)错(beng)误(kui),希望这些惨痛教训对读者们今后的Linux C编程具有一定的警示作用。
“swap”型指针错误
还是从最开始的swap函数错误写法说起:
#include <stdio.h>
void swap(int num_1, int num_2);
int main(int argc, char **argv)
{
int num_1 = 23;
int num_2 = 34;
printf("Before the change:nnum_1=%d, num_2=%dn", num_1, num_2);
swap(num_1, num_2);
printf("After changing:nnum_1=%d, num_2=%dn", num_1, num_2);
return 0;
}
void swap(int num_1, int num_2)
{
int temp = num_1;
num_1 = num_2;
num_2 = temp;
}
上述代码的改正措施就是在swap的参数列表里传入num_1与num_2的地址,然后再对两指针指向的内存单元进行值交换操作。初步了解了C语言的读者就已经知道了这种写法的错误根源,在于自定义swap函数与main函数中对应的num_1、num_2操作单元的不一致,即两个函数里定义与操作的变量是存在于不同的两块存储空间里的。因而发生下面这种指针错误就不足为奇了:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void stringSet(char *str);
int main(int argc, char **argv)
{
char *str = NULL;
stringSet(str);
printf("%sn", str);
}
void stringSet(char *str)
{
str = (char *)malloc(256);
strncpy(str, "This is a wrong example!", 256);
}
将此程序放在Linux上一执行,程序立马挂掉,并抛出“Segmentation Default”的错误。将此程序放在GDB调试器上一调试,就会发现程序执行到第12行时,str的值仍然是0x00,即NULL。就着之前swap函数的例子,多数经验欠缺的读者也许会疑惑:stringSet这个函数里面传入的不也是指针型变量吗,但为何不能成功让str指向一个malloc分配的空间并给它赋值呢?要说明白其中的道理,就不得不从内存的角度去解释其中的本质:
先来解释一下程序的大致运行流程:
- 程序一执行,就会在内存里给main()主体里分配一块区域,这块区域里就会有在main函数里面定义的空指针str(如上图);
- 当程序执行到stringSet()处时,此时操作系统就会在内存里分配另一块区域给stringSet,并在这个区域里面完成对此区域中stringSet的操作,执行stringSet函数结束,这块区域被操作系统回收,此区域的str不复存在;
- 执行完stringSet后,回到主函数main里面,继续执行下面的代码,之后main函数里str未改变,所以值依旧是NULL。
可见,stringSet函数里的str是因为stringSet函数的空间未关联上main这块空间进而没改变main里面str的值,这是stringSet函数错误的本质原因,而swap函数的改正版本——将main函数里num_1和num_2的地址作为参数传入swap函数,就是告诉处在swap函数那片空间的num_1和num_2指针,去操作地址为main函数里的num_1和num_2,这样一来,二者便产生了关联,也就成功进行了操作。所以说,修正stringSet的方法,就是把主函数里str指针的地址——二级指针&str作为参数传入stringSet函数,然后进行后续的操作,这样才是正确的写法。
修改只读数据段:
先说一下一个C程序在内存上的布局:
各个变量在程序运行时所在存储空间的位置如下:
局部变量——存在于栈区,程序运行离开所在程序段的花括号之后,该区域的内存会被操作系统回收,局部变量的生存周期仅限于对应程序段的花括号内;
malloc或realloc分配出的空间——存在于堆区,由用户自己管理,不需使用时,用free释放,若在程序中已经未使用而且未free,则有可能出现内存泄漏;
*.rodata(只读数据段)——存放用户自定义的宏或字符串常量(char ptr = “hello”)、const型常量。
上图详细的说明了各种类型的变量在程序运行时在内存上的布局。由此可见,像数据段这种存放常量和全局变量的区域,在程序运行时是受操作系统保护的,因为其生存周期长,在程序运行时不易被操作系统释放。所以下面这种操作就属于非法的指针操作了:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char *pc = "There is a exception in the program!n";
char *pc_1 = strstr(pc, "a ");
printf("%s", pc);
*(++pc_1) = 'n';
printf("%s", pc);
return 0;
}
以上代码就是由于用指针修改了只读数据段而产生的异常,程序在执行完第11行代码后就因为异常而终止。代码的第六行定义了一个指针pc,然后再让pc指向一个字符串首地址,这行代码的含义不是说的对指针pc进行初始化,而是让指针pc指向只读数据段中的一片区域,由于此区域在程序运行时受操作系统保护,所以之后的自行修改这个端的内容就会产生异常。而要修正此程序,只需将pc定义为一个字符数组即可,因为这个数组是局部变量,存放在程序存储空间里的栈区,用户自行修改此区域的内容是合法的。
局部数组返回型
就着上面一节里面的程序内存布局图,我们可以得出结论,变量在程序运行时的生存周期不同,决定了用户定义变量时的位置。局部变量的生存周期是最短的,因而把握不好变量定义的位置,就极易引起以下的指针异常:
#include <stdio.h>
#include <stdlib.h>
char *stringSet();
int main(int argc, char **argv)
{
printf("%sn", stringSet());
}
char *stringSet()
{
char str[] = "This is a wrong example!";
return str;
}
上述程序在编译之后操作系统也许会给出警告,但警告的意思不太明确,当程序一运行,便立马产生指针错误了。按照之前所说的,str数组在stringSet函数体里定义,属于局部变量,返回的str是一个指针,它指向数组str的首地址,离开stringSet的花括号,str的首地址虽然返回了,但是str所在的存储空间却被操作系统释放了,所以这时候的str空间是一个非法的内存空间,之后程序想打印出这个数组的内容,便是访问了非法空间,造成了指针错误。
一般来说,修正上述程序的方法有两种,即定义str数组为全局变量,或者定义一个str char指针,指向一段由malloc分配的空间,然后在对这段内存进行操作。但是C程序设计有着一个基本原则,那就是在程序设计时少用全局变量,所有前者的改进方法一般不提倡。
自定义非法地址
这种指针错误在算法型程序设计里面比较少见,但是在单片机编程和ARM-Linux编程里面比较多见,错误的原因在于用户自作主张随意定义了一个地址,然后对该地址所在的内存区域进行了操作。
#include <stdio.h>
int main(int argc, char **argv)
{
int *gpio_p1 = (int *)0xDEAF8534;
printf("&gpio_p1 = %6pn", gpio_p1);
*gpio_p1 = 28;
printf("gpio_p1 = %dn", *gpio_p1);
return 0;
}
以上的代码里,0xDEAF8534就是一个非法的地址,之后对该地址的任何操作都是非法的,所有产生了指针错误。
总结
简单说来,初学者容易犯的指针错误无非就是一下两点:
- 操作了非法地址所在的内存单元;
- 修改了只读数据段的内容;
第一点里面的小类型却是五花八门的,归结起来也是两种:即操作了指向NULL的指针所在的内存区域,和操作了未经操作系统合法分配的内存区域。
纵使是C语言编程的老手,也有可能在不经意间犯指针错误。当程序产生了指针错误时,不必惊慌,因为Linux C自有GDB调试器,程序员可以通过设置断点的方式打印程序里指针的值,进而推断指针错误产生的位置,从而进行修正(GDB是一个非常好用的程序调试工具,本人所有程序里的指针错误均修正于此)
最后
以上就是虚心黑裤为你收集整理的盘点:常见的Linux C指针错误的全部内容,希望文章能够帮你解决盘点:常见的Linux C指针错误所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复