Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what is the difference between extern char **environ and extern char *environ[]

Tags:

c

linux

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

extern char *environ[];
int main(int argc, char *argv[])
{
    int index = 0;
    char **env = environ;
    printf("Environment variables:\n");
    index = 0;
    while (env[index])
    {
        printf("envp[%d]: %s\n", index, env[index]);
        ++index;
    }
    return 0;
}

output:

Environment variables:  
envp[0]: GH#þ 

I want to print all the environments, but it can't work.
I changed extern char *environ[] to extern char **environ and it can print all the environments.
After changing the code output:

Environment variables:
envp[0]: XDG_SESSION_ID=8
envp[1]: TERM=xterm
envp[2]: SHELL=/bin/bash
envp[3]: SSH_CLIENT=192.168.1.224 1085 22
envp[4]: SSH_TTY=/dev/pts/0
...
like image 480
zhangke Avatar asked Dec 10 '22 09:12

zhangke


1 Answers

I find this stuff impossible to follow without considering what is actually in memory, and how it is organized.

Somewhere in memory are a bunch of blocks of data that contain environment strings -- "foo=bar", "hello=world", and so on. Let's suppose for the sake of argument that "foo=bar" is at address 100, and "hello=world" is at address 200.

Somewhere else in memory is another block of data that lists the addresses of those data blocks (often followed by a zero, so we know where the list actually ends, if we don't know the size in advance}. So this block contains the data

{100,200,0}

If I defined this data as char *env[] that would mean that there is a place in memory called env, and at that place is the actual data block {100,200,0}. That is, the data at env itself would be the number 100, which is the address of one of the environment strings. The next location after env would contain 200, and the next after that 0 (OK, I'm simplifying a bit).

If I defined the data block {100,200,0} as a char **, that would mean there is a place in memory called env that contains the address of the data block {100,200,0}. The data stored at env would not be "100" (the address of a string). It would be an address that indicates the start of the data block {100,200,0}.

In a C program, the environment is, in fact, a char **, that is, the thing called environ is not the start of a list of addresses of strings, it is the address of a list of addresses of strings. To see that, you can define it wrongly, and then correct it, as in this modified version of the original code:

extern char *environ[];
int main(int argc, char *argv[])
{
    int index = 0;
    char **env = (char **)environ[0];
    printf("Environment variables:\n");
    index = 0;
    while (env[index])
    {
        printf("envp[%d]: %s\n", index, env[index]);
        ++index;
    }
    return 0;
}

Using char *environ[] tells the compiler that (wrongly) the location called environ begins a list of pointers to strings, arranged one after another in memory from that point onwards. In fact, only the data at the exact location environ is relevant. We can think of that data as environ[0], and just cast it to the real data type, which is a char **.

The differences between a char ** and a char *[] are eroded, and thus made confusing, by the fact that C won't allow an array to be passed to a function. All arguments to functions are a single number -- either an primitive like an integer or float, or the address of something. If you try to pass an array (that is, a block of data), the address of the start of the block is passed instead. This means that in most code, you can actually use char ** and char *[] as if they were the same thing. You get problems -- as in this question -- when the data is arranged in memory in a certain way, and the compiler cannot work out what that way is unless the programmer tells it by using the proper type declaration.

like image 97
Kevin Boone Avatar answered Apr 28 '23 09:04

Kevin Boone