fwr nr
fwr nr

Reputation: 697

How does a C Compiler know that char** x points to an array?

In C, I've read that char** x and char* x[] mean exactly the same thing. I understand the 2nd example, but I do not understand the 1st example.

To me the first example is saying "pointer to a pointer to a character", but C will see this as "point to a list of pointers to character arrays"?

Would someone be able to explain this to me in layman's terms as I've been having an unusually difficult time grasping it.

EDIT: I admit I worded the first bit as what I thought people would want to hear and not my actual interpretation. I too see it as a pointer to a pointer to a char but my understanding fell when I read the top answer on this question: Difference between char *argv[] and char **argv for the second argument to main()

Which shows that it is being used like a pointer to an array of character pointers when used as an argument vector...

Upvotes: 5

Views: 649

Answers (5)

John Bode
John Bode

Reputation: 123596

In the context of a function parameter declaration, T a[N] and T a[] are interpreted as T *a - all three declare a as a pointer to T:

void foo( T a[N] )

is the same as

void foo( T a[] )

is the same as

void foo( T *a )

So, replace T with char *, and you get

void foo( char *arr[N] )

is the same as

void foo( char *arr[] )

is the same as

void foo( char **arr )

This is only true for a function parameter declaration - in a regular declaration,

T a[N];

is not the same as

T *a;

So why is this the case?

Unless it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element in the array.

So, if you declare an array as

T a[N];

and pass it to a function as

foo( a );

the expression a in the function call is converted from type "N-element array of T" to "pointer to T", and what the function foo actually receives is a pointer value, not an array:

void foo( T *a ) { ... }

The use of a[] in place of *a is a holdover from the B programming language, from which C was derived. A lot of C's array weirdness can be traced back to B, frankly. Personally, I think it creates more confusion than it's worth, but I'm not on the committee that writes the standard.

You will sometimes hear someone proclaim that "an array is just a pointer" - that's a misinterpretation of the rule. Arrays and pointers are not the same, but array expressions will be converted to pointer expressions in most circumstances.

Upvotes: 0

user2736738
user2736738

Reputation: 30936

Long story short - it doesn't distinguish that. Compiler doesn't know that it points to an array. All it sees is a pointer. The way you have shown(as part of function parameter declaration - passing to the function) char *[] and char ** denotes the same thing. What it is? Pointer to a char*. Nothing more than that.

There is no extra information kept by a compiler that makes it distinguish whether you passed a char* array or a char** because ultimately char* array decays into pointer to first element - which is char**.

Try this, you would understand:-

char c = 'a';
char *pc = &c;
char **ppc = &pc;
f(ppc);

....

void f(char *ppc[]){

}

This won't make compiler complain. Because it is ultimately considered by compiler as char **ppc.

To prove that arrays are not seen by the compiler when it is passed to the function - you can see this quote (which basically says that there is decay and it boils down to pointer to first element. So it sees a pointer that's all) (This by no way give the idea that char** and char*[] are same in all cases because of decaying - it is not)

§6.3.2.1¶3 standard N1570

Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type 'array of type' is converted to an expression with type 'pointer to type' that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

The edit is been made to clear the misinterpretation that got someone to not appreciate the answer.

Upvotes: 3

Kaz
Kaz

Reputation: 58666

Firstly, char **x and char *x[] are not equivalent, except as function parameter declarations.

Equivalent:

int main(int argc, char **argv);
int main(int argc, char *argv[]); // equivalent

Not equivalent:

{
   char **pointer;
   char *array[3];

}

A C T * pointer can point to a single object of type T, or it can point to any of the elements of an "array of T", and can also point one element beyond the end of such an array.

This information isn't contained in the pointer's language-level, programmer-visible type information.

In some cases a compiler can deduce these situations and provide useful diagnosis if mistakes are made.

A compiler can trace the data flow of where an expression's value came from.

For instance, given:

char *array[3];      /* array of 3 "char *" pointers */
char **ptr = array;  /* pointer to array[0] */

Here, the compiler can analyze that ptr was initialized from array. And so between that initialization and any other point in the program, if no assignment to ptr takes place, it can be assumed to still be pointing to array[0].

So it is possible for the compiler to produce a warning for a dubious expression like ptr + 5, while remaining silent for ptr + 3.

ISO C doesn't require any such analysis or diagnostic, though; it's a "quality of implementation" matter.

There are limitations to how much can be diagnosed statically. C functions can receive pointer values as parameters "out of nowhere", about which nothing can be reasonably be known at compile time.


By the way, the declaration style char* ptr is a bad habit; it should be styled as char *ptr. Note that char* a, b declares a as a pointer and b as char. The syntax is that char is a list of "declaration specifiers". These specifiers are followed by a comma-separated list of "declarators". These declarators are *a and b, respectively. If you write char* ptr, you're putting a whitespace division in the middle of the declarator, while clumping it together with the specifier.

This is a bit like taking the arithmetic expression x + x*y + y and writing it as x+x * y+y, so it looks like multiplication is done last.

Avoid such misleading whitespace in programming.

Upvotes: 1

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385405

How does a C Compiler know that char** x points to an array?

It doesn't!

To me the first example is saying "pointer to a pointer to a character"

Correct.

but C will see this as "point to a list of pointers to character arrays"?

Also correct.

The trick is that although the language doesn't know how long the list is, a list with just one thing in it is still a list. So it doesn't really matter.1

Given a pointer T* ptr, *ptr will either give you the one object that ptr points to (out of one), or it will give you the first object that ptr points to (out of more than one).

It's only if you start incrementing that pointer (e.g. ptr++, or during access like ptr[152]) that it matters how many objects are actually at the other end. Again, though, it doesn't matter to the language, because that's left up to the programmer to get right.

1. There do exist pointer-to-array types, so it is possible to bring the type system into the discussion and then it does matter. But we are only discussing pointer-to-single-object types (despite the linked-to answer making a big mess of the subject as regards "passing" arrays by value).


If it helps, the opening statement for the top answer on the question you linked to:

Basically, char* argv[] means array of char pointers, whereas char** argv means pointer to a char pointer.

…is wrong. char* argv[] is transparently respelled as char** argv (thanks a lot, C!) so both of them mean literally the same thing.

You should read all the answers under a question, not just one; bmargulies's is much more accurate.

Upvotes: 2

R Sahu
R Sahu

Reputation: 206747

To simplify the explanation, let's replace char* with TypeName, which an be anything.

TypeName* x and TypeName x[] are exactly the same when they are used as function parameters.

Some use the first form to indicate a pointer to a single object and the second form to indicate a pointer to the the first element of an array. Form the compiler's point of view though, they are exactly the same.

Say you have a variable:

TypeName a[10];

When a is used as an argument in a function call, the decays to the pointer to the first alement

 foo(a);

is the same as:

foo(&a[0]);

In the function, the parameter can be declared as either TypeName* x or TypeName x[].

You can access the elements of the array in a using the array syntax, x[i], regardless of how the parameter is declared.

void foo(TypeName* x)
{
    x[0] = <some expression>;
}

and

void foo(TypeName x[])
{
    x[0] = <some expression>;
}

are same. They assign a value to the the first element of the array.

Upvotes: 0

Related Questions