Quando um programa é iniciado, ele recebe seu ambiente como uma matriz de ponteiros para algumas strings no formato var=value
. No Linux, esses estão localizados na parte inferior da pilha. Na parte inferior, você tem todas as strings dobradas uma após a outra (isso é mostrado em /proc/pid/environ
). E acima você tem uma matriz de ponteiros (NULL terminados) para essas cadeias (é isso que entra em char *envp[]
em seu int main(int argc, char* argv[], char* envp[])
, e a libc geralmente inicializa environ
para).
putenv()
/ setenv()
/ unsetenv()
, não modifique essas cadeias, elas nem geralmente modificam os ponteiros. Em alguns sistemas, esses (strings e ponteiros) são somente leitura.
Enquanto a libc geralmente inicializa char **environ
para o endereço do primeiro ponteiro acima, qualquer modificação do ambiente (e essas são para futuros executivos), geralmente fará com que uma nova matriz de ponteiros seja criada e atribuída a environ
.
Se environ
for inicialmente [a,b,c,d,NULL]
, onde a
é um ponteiro para x=1
, b
para y=2
, c
para z=3
, d
para q=5
, se você faz um unsetenv("y")
, environ
teria que se tornar [a,c,d,NULL]
. Nos sistemas em que a lista inicial de matriz é somente leitura, uma nova lista teria que ser alocada e atribuída a environ
e [a,c,d,NULL]
armazenadas nela. No próximo unsetenv()
, a lista poderia ser modificada no lugar. Somente se você tiver unsetenv("x")
acima, uma lista não poderá ser realocada ( environ
poderia ser incrementado para apontar para &envp[1]
. Não sei se algumas implementações de libc realmente executam essa otimização).
Em qualquer caso, não há motivo para que as cadeias armazenadas na parte inferior da pilha sejam modificadas de qualquer forma. Mesmo que uma implementação de unsetenv()
estivesse realmente modificando os dados inicialmente recebidos na pilha no local, isso apenas modificaria os ponteiros, não haveria todo o trabalho de também apagar as seqüências apontadas. (que parece ser o que o GNU libc faz em sistemas Linux (com executáveis ELF, pelo menos), ele modifica a lista de ponteiros em envp
no lugar, desde que o número de variáveis de ambiente não aumente.
Você pode observar o comportamento usando um programa como:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char* argv[], char* envp[]) {
char cmd[128];
int i;
printf("envp: %p environ: %p\n", envp, environ);
for (i = 0; envp[i]; i++)
printf(" envp[%d]: %p (%s)\n", i, envp[i], envp[i]);
#define DO(x) x; puts("\nAfter " #x "\n"); \
printf("envp: %p environ: %p\n", envp, environ); \
for (i = 0; environ[i]; i++) \
printf(" environ[%d]: %p (%s)\n", i, environ[i], environ[i])
DO(unsetenv("a"));
DO(setenv("b", "xxx", 1));
DO(setenv("c", "xxx", 1));
puts("\nAddress of heap and stack:");
sprintf(cmd, "grep -e stack -e heap /proc/%u/maps", getpid());
fflush(stdout);
system(cmd);
}
No Linux com o GNU libc (mesmo com klibc, musl libc ou dietlibc, exceto pelo fato de usar memória anônima mmap em vez do heap para memória alocada), quando executado como env -i a=1 x=3 ./e
, que fornece (comentários inline) :
envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
envp[0]: 0x7ffc2e7b4fec (a=1)
envp[1]: 0x7ffc2e7b4ff0 (x=3)
# envp[1] is almost at the bottom of the stack. I lied above in that
# there are more things like the path of the executable
# environ initially points to the same pointer list as envp
After unsetenv("a")
envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
environ[0]: 0x7ffc2e7b4ff0 (x=3)
# here, unsetenv has reused the envp[] list and has not allocated a new
# list. It has shifted the pointers though and not done the optimisation
# I mention above
After setenv("b", "xxx", 1)
envp: 0x7ffc2e7b3238 environ: 0x1bb3420
environ[0]: 0x7ffc2e7b4ff0 (x=3)
environ[1]: 0x1bb3440 (b=xxx)
# a new list has been allocated on the heap. (it could have reused the
# slot freed by unsetenv() above but didn't, Solaris' version does).
# the "b=xxx" string is also allocated on the heap.
After setenv("c", "xxx", 1)
envp: 0x7ffc2e7b3238 environ: 0x1bb3490
environ[0]: 0x7ffc2e7b4ff0 (x=3)
environ[1]: 0x1bb3440 (b=xxx)
environ[2]: 0x1bb3420 (c=xxx)
Address of heap and stack:
01bb3000-01bd4000 rw-p 00000000 00:00 [heap]
7ffc2e794000-7ffc2e7b5000 rw-p 00000000 00:00 0 [stack]
No FreeBSD (11-rc1 aqui), uma nova lista já está alocada em unsetenv()
. Não apenas isso, mas as próprias seqüências estão sendo copiadas para o heap, assim environ
é completamente desconectado do envp[]
que o programa recebeu na inicialização após a primeira modificação do ambiente:
envp: 0x7fffffffedd8 environ: 0x7fffffffedd8
envp[0]: 0x7fffffffef74 (x=2)
envp[1]: 0x7fffffffef78 (a=1)
After unsetenv("a")
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15008 (x=2)
After setenv("b", "xxx", 1)
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15018 (b=xxx)
environ[1]: 0x800e15008 (x=2)
After setenv("c", "xxx", 1)
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15020 (c=xxx)
environ[1]: 0x800e15018 (b=xxx)
environ[2]: 0x800e15008 (x=2)
No Solaris (11 aqui), vemos a otimização mencionada acima (onde unsetenv("a")
acaba sendo feito com environ++
), o slot liberado por unsetenv()
sendo reutilizado por b
, mas é claro que um Uma nova lista de ponteiros deve ser alocada após a inserção de uma nova variável de ambiente ( c
):
envp: 0xfeffef6c environ: 0xfeffef6c
envp[0]: 0xfeffefec (a=1)
envp[1]: 0xfeffeff0 (x=2)
After unsetenv("a")
envp: 0xfeffef6c environ: 0xfeffef70
environ[0]: 0xfeffeff0 (x=2)
After setenv("b", "xxx", 1)
envp: 0xfeffef6c environ: 0xfeffef6c
environ[0]: 0x806145c (b=xxx)
environ[1]: 0xfeffeff0 (x=2)
After setenv("c", "xxx", 1)
envp: 0xfeffef6c environ: 0x8061c48
environ[0]: 0x8061474 (c=xxx)
environ[1]: 0x806145c (b=xxx)
environ[2]: 0xfeffeff0 (x=2)