Hello, Environment

The operating system keeps several variables in memory, variables that hold information necessary to running the computer. For example, the path variable lists directories in which the operating system looks for programs. The prompt variable describes how to display the command prompt. Some programs even create variables, allowing the programs to save configuration or other information.

To read the environment, your program uses the environ pointer array. It must be declared as an external variable, as shown in the following code:

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main()
{
    while(*environ)
    {
        printf("%s\n",*environ);
        environ++;
    }
    return(0);
}

The code above dumps the environment by plowing through the environ array one string at a time. (The environ variable is declared in the stdlib.h header file.)

When you know a specific value to fetch, you use the getenv() function, as demonstrated in the following code:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *searchpath;

    searchpath = getenv("PATH");
    printf("The search path is '%s'\n",searchpath);
    return(0);
}

The code above uses the getenv() function to fetch the PATH environment variable. A pointer to that variable is returned, which the program stores in the local address searchpath. Further manipulation of the path would require additional code, for example, to check to see whether a specific directory is on the path or not.

While you could manipulate the environment by checking the environ variable, the man pages recommend that you use getenv() to fetch items. More importantly, you should use the putenv() function to add an item to the environment or the setenv() or unsetenv() functions to change an item.

8 thoughts on “Hello, Environment

  1. While this works, I think itʼs worth pointing out, that accessing the externally defined ‘environ’ variable is a POSIX-specific thing, thatʼs not backed by the “C” standard.

    And while the Windows APIs (for example) also export such a variable, they do so under a somewhat different name—also, on Linux this variable gets defined in <unistd.h> while on Windows one has to include <stdlib.h>:

    #define _GNU_SOURCE /* → __USE_GNU gets defined which results in ‘extern */
    #include <stdio.h> /* char **environ;’ getting defined in <unistd.h> */
    #include <stdlib.h>

    #if defined(_WIN32)
    #include <stdlib.h> /* → declares ‘_CRTIMP extern char ** */
    #define environ _environ /* _environ;’ in non-Uʜɪᴄᴏᴅᴇ builds */
    #else
    #include <unistd.h> /* declares ‘extern char **environ;’ */
    #endif /* if __USE_GNU is defined */

    int main (int argc, char *argv [], char *envp [])
    { char **env = environ;

      /* (void) typecasts to silence unreferenced parameter warnings: */
      (void)argc;
      (void)argv;

      /* ‘envp’ points to the same environment block as ‘environ’: */
      env = envp;

      while (*env)
      {
        printf (“%s\n”, *env);
        env++;
      }

      return (0);
    }

    Iʼll readily admit that my cross-platform solution looks way messier than your example… most of the above code, however, is only really necessary to get the ‘environ’ variable declared in a plattform-agnostic way.

    If one is willing to step outside the safe waters of the standard, then I think relying on a non-standards conforming main() function (with an additional char *envp parameter) is the better route to go instead of all those messy #includeʼs and #defineʼs.

  2. Interesting. The man page on my installation says that it’s C89 and C99 compatible as well as POSIX. My code also compiles without using the unistd.h header. Weird.

  3. Admittedly: switching to the <unistd.h> (and, under Windows, <stdlib.h>) wouldnʼt really have been necessary. That being said, there is one argument that can be made for them: one never knows what additional compiler/platform dependent attributes get tagged on in OS-specific variants of include files–for example, MSVC likes its _environ declaration like so:

    __declspec(dllimport) extern char ** _environ;

    »The man page on my installation says that it’s C89 and C99 compatible as well as POSIX.«

    To be honest, I stumbled upon all of this because I usually try to compile the code you provide in MSVC (and it didnʼt work out of the box).

    This raised my suspicions and prompted me to investigate: looking through “n2176.pdf”, the final (publicly available) draft PDF for C17/C18, thereʼs no mention of “environ”… a description of which, on the other hand, can readily be found in the latest revision of the Single Uɴɪx™ Specification, as published by The Open Group.

  4. That’s really good info. I’ve made the mistake in the past of writing about compiler-dependent functions without checking other platforms. macOS has quite a few useful but non-standard C library extensions, such as strcasecmp(). I was rather ignorant thinking that they were standard just because the man page listed them.

  5. To be honest: I do think this truth about “C” — that every platform has introduced various idiosyncrasies over the past few decades — is really its biggest weakness.

    Working around these differences is a non-trivial thing in a large codebase and for sure one of those things that gives languages like Java, that come with a platform-independent standard library, a clear and definite edge.

    But I guess after all this time there is nothing but to to live with these past sins ☺

  6. Ah, yes, that book. I had already been programming in C for 15 years when I first read it, and I remember the thrill when I realized that C still had subtleties in store that were unbeknownst to me.

    If memory serves, I most enjoyed his bits on library interpositioning, how multidimensional arrays break into components and, of course, his
    Magic Decoder Ring for C Declarations.

    Sadly, with demand for C books (probably) going down, there most likely wonʼt be too many gems like van der Lindens book in the future… even I have to admit, that the book currently sitting on desk (waiting to be read), Programming Rust by Blandy Orendorf & Tindall, is not about C but Rust.

    That being said, I am looking forward to the final release of Jens Gustedtʼs Modern C and JeanHeyd Meneide just recently released an updated draft of C23, which is nice (especially since the final ISO standard will most likely again cost in the range of $225 USD).

Leave a Reply