setenv和unsetenv如何更改堆栈?

问题描述

我正在阅读一本有关操纵环境数组的函数的教科书:

如果环境数组包含形式为name = oldvalue的字符串,则 unsetenv将其删除,setenv将oldvalue替换为newvalue,但仅当 覆盖不为零。如果名称不存在,则setenv添加name = newvalue 到数组

int setenv(const char *name,const char *newvalue,int overwrite);

void unsetenv(const char *name);

和下面的图片描述了新程序启动时用户堆栈的组织。

enter image description here

我的问题是:

Q1-假设我们使用unsetenv删除envp一个元素,这不是通过用envp替换原始值在NULL中造成空白吗?由于该值为NULL,因此它将淹没该值之后的所有后续环境元素?

Q2-如果我们使用setenv用newvalue替换旧值,如果newvalue的长度大于oldvalue,那么如果setenv只是修改“空终止的环境变量字符串”。所以我的猜测是,将会有一个新的元素被压入堆栈,并且envp中相应的指针元素也会相应地更改,我的理解正确吗?

解决方法

下面的图片描述了新程序启动时用户堆栈的组织

那是正确的,但是假设env API仅限于操作内存是不正确的。实现可以自由复制环境变量,实际上大多数实现都可以这样做。这是一个完全实现此目的的libc实现示例。

https://code.woboq.org/userspace/glibc/stdlib/setenv.c.html#__add_to_environ

/* This function is used by `setenv' and `putenv'.... */
__add_to_environ (const char *name,const char *value,const char *combined,int replace)
{  
    ....
    new_environ = (char **) realloc (last_environ,(size + 2) * sizeof (char *));      
    ....
    if (__environ != last_environ)
        memcpy ((char *) new_environ,(char *) __environ,size * sizeof (char *));
    ....
    last_environ = __environ = new_environ;
,

setenvunsetenv不是C标准的一部分;该标准仅提供getenv,它“搜索由主机环境提供的环境列表”,没有关于如何创建该列表的说明。用于操作环境列表的功能将在Posix标准中找到(以及用于其他不伪装与Posix兼容的实现的地方)。

Posix还指定全局变量environ,该变量指向当前环境列表。输入main()时:

以下变量,如果要直接使用,则必须由用户声明:

extern char **environ;

初始化为指向环境字符串的字符指针数组的指针。

碰巧,许多操作系统在调用main()之前都将初始环境列表放到了堆栈上,尽管任何标准都不要求这样做。此外,environ的值不是固定的:

应用程序可以通过分配environ变量以指向指向新环境字符串的字符指针数组来在单个操作中更改整个环境。

但是这种自由伴随着很多警告:

在为environ分配了新值之后,应用程序不应依赖环境其余部分的新环境字符串,而应调用getenv()putenv(),{{1} },setenv()或任何依赖于环境变量的函数,可能会注意到unsetenv()已更改,将环境字符串复制到新数组并分配environ指向它

此外:

任何直接修改environ变量所指向的指针具有未定义行为的应用程序。

实际上,某些实现确实利用了这种灵活性,因此需要考虑那些警告。如果创建自己的environ,那么管理动态内存将非常棘手,因此您可能不得不对可能的内存泄漏采取放松的态度(尽管标准库本身可能会更加谨慎地管理自己的内存)。

在Linux或BSD系统上,您应该能够使用environ命令来查找更多本地特定的信息。 Posix标准本身包含description of the exec* interfaces中的信息。 getenv() description的“理性”部分也值得一读。

经过五十年的事后调查,很容易批评这种设计。尽管如此,它基本上已经为整个世界服务良好,而且它根深蒂固,以至于很难看到如何改变它。所以不管喜欢与否,都有。