在unistd.h中定义了变量char **environ;来表示当前所有环境变量,一般来说访问特定环境变量可以用getenv,但是想遍历所有环境变量就得使用environ。
即在程序内全局声明extern char **environ;当然设定main函数第3个参数也可以,不过不推荐,因为ISO C的main函数没有第三个参数。
environ维护了一个char*数组,每个元素都是一个指针指向函数栈帧顶部的环境变量,数组结尾是NULL。
于是正确的遍历姿势是下面这样
for (int i = 0; environ[i] != NULL; i++)
puts(environ[i]);
然后我试了下错误的姿势
for (char *ptr = environ[0]; ptr; ptr++)
puts(ptr);
结果是程序dump了,审查了下发现错误出在ptr++上,因为ptr类型是char*,执行++后指针只向前移动1 byte。
于是就变成这样
for (char *ptr = environ[0]; ptr; ptr += (strlen(ptr) + 1)
puts(environ[i]);
代码已经比较丑陋了,而且还多出了不必要的计算,即strlen函数,但是程序依然dump了。
我的调试方式是这样的
for (char *ptr = environ[0]; ptr; ptr += (strlen(ptr) + 1))
{
static int i = 0;
if (strcmp(ptr, environ[i]) != 0)
{
printf("error: %d\n", i);
break;
}
puts(ptr);
i++;
}
错误如下
Program received signal SIGSEGV, Segmentation fault.
__strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:
../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S: No such file or directory.
对啊,environ数组最后一个元素是NULL,但是strcmp必须接收非NULL指针作为参数(因为strcmp的参数s1、s2必须可以用*s1、*s2来访问,NULL是地址0,是用户无法访问的地址,用户访问无法访问的地址时就会产生SIGSEGV信号)。
于是我定位到了strcmp这句
(gdb) b if environ[i]==
(gdb) p ptr
$ = 0x7fffffffefe3 "/home/xyz/TLPI/a.out"
(gdb) p environ[i]
$ = 0x0
原因也清楚了。在C程序的存储空间高地址是命令行参数和环境变量依次排列,如下图
n1是环境变量的数量,n2是命令行参数的数量。因此在ptr指向最后一个环境变量时,ptr+=(strlen[ptr]+1)后指向的是argv[0]。
字符指针数组environ保存了n1+1个元素,多出一个元素是NULL。而ptr+=(strlen[ptr]+1)则是直接访问程序的存储空间,并没有一个终止符。
当ptr到达内存中不可访问的区域(即argv[n2-1]的下面,函数栈帧的地址),就会引发SIGSEGV信号。