1

I know that the environment is essentially an array of pointers to strings. Hence, freeing of memory occupied by these string would lead to loss of these environment variables.

If I allocate memory for an environment string and then use putenv() to set a variable which already exists, does it lead to a memory leak?

Demonstrated in following C program:

char ** environ;

main()
{
  /* 
     Code for printing all environment variables
  */ 

  char * temp = (char *)malloc( 64 * sizeof(char));
  strcat(temp, "");
  strcat(temp, "PWD=/home/mycomputer/");
  putenv(temp);

  /*
     Code for printing all environment variables 
  */
}

Here the PWD environment variable was already pointing to some memory(which contained the path) and now I have allocated more memory(which contains a path). So the pointer shifts to the new location and the old memory is inaccessible but still allocated. So it is correct if I say that there has been a memory leak in the program? If not, then how is this change being accommodated in the memory?

  • 1
    Is this in a program you're writing (if so, please demonstrate), or at the shell level? – Jeff Schaller Aug 13 '19 at 19:52
  • It's too weird. You didn't ensure temp is null terminated, then how can you expect strcat behavior? Also, putenv will copy data and handle memory for environment variables anyway. But since you didn't specify a proper argument, behavior is undefined. – 炸鱼薯条德里克 Aug 14 '19 at 1:29
  • I can certainly come up with different ways to handle environment variable space by the operating system, giving different answers... – vonbrand Aug 14 '19 at 13:39
  • 1
    @VaradBhatnagar, if you mean putenv() in particular, you should edit your answer to say so. Or if you mean both setenv() and putenv(), then please clarify that. – ilkkachu Aug 14 '19 at 16:11
  • @ilkkachu Done that. Thanks for the observation! – Varad Bhatnagar Aug 14 '19 at 16:16
2

Your observation is correct. The new string you have allocated with malloc replaces the old string, and the old string is not used anymore. If the old string is part of the environment passed when the program is started (i.e. this is the first time the variable is changed in this process), then the old string is part of the stack segment.

If this is a memory leak or not is debatable. You can't free the memory occupied by the old string, but there is also no sensible way to reuse this memory. If you call putenv several times, you can of course free the memory you have allocated yourself when the strings are not used anymore.

Note that the semantics of putenv depends on the version of glibc, as documented in the manual page.

Btw, there is a bug in your program, instead of

strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");

you should write

strcpy(temp, "PWD=/home/mycomputer/");
1

It's a shame that you've reworked your question from the first version which asked about setenv() because setenv() does leak memory, at least in the glibc implementation.

Take this example program; it will grow and grow until it eats up all the memory it can:

#include <stdlib.h>
#include <stdio.h>
int main(void){
        char buf[24];
        for(;;){
                snprintf(buf, sizeof buf, "%d", rand());
                setenv("foo", buf, 1);
        }
}

In the glibc implementation setenv() will never free the memory already allocated by a previous setenv(); it will however avoid duplication by keeping track of the environment strings allocated by it in a binary tree (using tfind(3), separate from the char **environ list). That also has the nice effect of hiding the leak from tools like valgrind ;-)


As to putenv(), it relies on the caller to manage to string passed as an argument to it. So it's the caller business to make sure that it doesn't leak it when repeately calling putenv(), that it doesn't free it while it's still part of the environment, and that it's not using an automatic/stack variable for it.

Also, modifying the string it in place after calling putenv() may remove an environment variable or add another one. Example:

#define _XOPEN_SOURCE   500
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <unistd.h>
int main(void){
        extern char **environ;
        static char *my_environ[] = { "YUCK=yumm", 0 };
        static char str[256] = "FOO=bar";
        environ = my_environ;
        putenv(str);
        snprintf(str, sizeof str, "BAZ=quux");
        execl("/usr/bin/printenv", "printenv", (void*)0);
        err(1, "execlp");
}

This is required by the standard, but didn't work in older versions of glibc, where putenv() would make a copy of its argument.


Finally, both setenv() and putenv() will relocate the char **environ array when they are adding a new variable to the environment. The implementation will keep their own copy of the pointer and grow it with realloc(), but will not mess with its original (which points to static memory), or with memory allocated by hand with eg. environ = calloc(sizeof *environ, envlen).

1

The environment consists of three things.

  1. A double pointer, say envp, called environment list pointer.

  2. This points to environment list, which you can imagine to be an array of pointers, which points to the environment strings.

  3. The environment strings, which are of the form name=value.

Now, where is this stored in the VM layout of the process?

The answer is - above the stack; we can call it the higher address.

This has a concrete ceiling as well as concrete bottom; since above, you cannot expand because it is the ceiling of the layout; below you cannot expand because of presence of stack.

Now, coming to your question --

When you change a string to any value that's greater than the length the higher segment can accommodate, it internally, essentially does a malloc.

Then, the pointer in the environment list is changed to point to that malloc'ed memory. So, you will have an environment list, where some pointers are still pointing to the addresses in the higher memory while this particular pointer will point to an area on the heap. In this particular case of modification of a parameter, the whole environment list is not copied to the heap.

What happens to the previous memory the pointer was point to? Well, it looks like a memory leak, but is typically not a leak, since that memory was already there and is now a useless chunk, that cannot be pointed to anymore.

I wouldn't call it a memory leak because it was not dynamic allocation, but static which was already there; part of a reserved segment.

A memory leak sounds like some part of memory which you could use for something else, but cannot, since there's no way to reach it. But this piece of memory in the higher address was anyways not available to you for anything else in the first place.

So, it looks like a memory leak; but I would not call it typically that!

Apologies for the confusion, I have tried to be as clear as I could be :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.