Related
I was learning about pointers and strings.
I understood that,
Pointers and Arrays/Strings have similar behaviours.
array[] , *array , &array[0]. They all are one and the same.
Why does the three statements in this code work, and char * help one does not ?
#include <stdio.h>
void display(char*help){
for(int i=0; help[i]!='\0'; i++){
printf("%c", help[i]);
}
}
int main(){
// char help[] = "Help_Me"; //Works
// char help[] = {'H','e','l','p','_','M','e','\0'}; //Works
// char *help = "Help_Me"; //Works
char *help = {'H','e','l','p','_','M','e','\0'}; //Error
display(help);
}
Error Messages :
warning: initialization of 'char *' from 'int' makes pointer from integer without a cast
warning: excess elements in scalar initializer
Pointers and Arrays/Strings have similar behaviours.
Actually, no, I wouldn't agree with that. It is an oversimplification that hides important details. The true situation is that arrays have almost no behaviors of their own, but in most contexts, an lvalue designating an array is automatically converted to pointer to the first array element. The resulting pointer behaves like a pointer, of course, which is what may present the appearance that pointers and arrays have similar behaviors.
Additionally, arrays are objects, whereas strings are certain configurations of data that char arrays can contain. Although people sometimes conflate strings with the arrays containing them or with pointers to their first elements, that is not formally correct.
array[] , *array , &array[0]. They all are one and the same.
No, not at all, though the differences depend on the context in which those appear:
In a declaration of array (other than in a function prototype),
type array[] declares array as an array of type whose size will be determined from its initializer;
type *array declares array as a pointer to type; and
&array[0] is not part of any valid declaration of array.
In a function prototype,
type array[] is "adjusted" automatically as if it were type *array, and it therefore declares array as a pointer to type;
type *array declares array as a pointer to type; and
&array[0] is not part of any valid declaration of array.
In an expression,
array[] is invalid;
*array is equivalent to array[0], which designates the first element of array; and
&array[0] is a pointer to array[0].
Now, you ask,
Why does the three statements in this code work, and char * help one does not ?
"Help_Me" is a string literal. It designates a statically-allocated array just large enough to contain the specified characters plus a string terminator. As an array-valued expression, in most contexts it is converted to a pointer to its first element, and such a pointer is of the correct type for use in ...
// char *help = "Help_Me"; //Works
But the appearance of a string literal as the initializer of a char array ...
// char help[] = "Help_Me"; //Works
... is one of the few contexts where an array value is not automatically converted to a pointer. In that context, the elements of the array designated by the string literal are used to initialize the the array being declared, very much like ...
// char help[] = {'H','e','l','p','_','M','e','\0'}; //Works
. There, {'H','e','l','p','_','M','e','\0'} is an array initializer specifying values for 8 array elements. Note well that taken as a whole, it is not itself a value, just a syntactic container for eight values of type int (in C) or char (in C++).
And that's why this ...
char *help = {'H','e','l','p','_','M','e','\0'}; //Error
... does not make sense. There, help is a scalar object, not an array or a structure, so it takes only one value. And that value is of type char *. The warnings delivered by your compiler are telling you that eight values have been presented instead of one, and they have, or at least the one used for the initialization has, type int instead of type char *.
array[] , *array , &array[0]. They all are one and the same.
No. Presuming array names some array, array[] cannot be used in an expression (except where it might appear in some type description, such as a cast).
array by itself in an expression is automatically converted to a pointer to its first element except when it is the operand of sizeof or the operand of unary &. (Also, a string literal, such as "abc", denotes an array, and this array has another exception to when it is converted: When it is used to initialize an array.)
In *array, array will be automatically converted to a pointer, and then * refers to the element it points to. Thus *array refers to an element in an array; it is not a pointer to the array or its elements.
In &array[0], array[0] refers to the first element of the array, and then & takes its address, so &array[0] is a pointer to the first element of the array. This makes it equivalent to array in expressions, with the exceptions noted above. For example, void *p = array; and void *p = &array[0]; will initialize p to the same thing, a pointer to the first element of the array, because of the automatic conversion. However, size_t s = sizeof array; and size_t s = sizeof &array[0]; may initialize s to different values—the first to the size of the entire array and the second to the size of a pointer.
// char help[] = "Help_Me"; //Works
help is an array of char, and character arrays can be initialized with a string literal. This is a special rule for initializations.
// char help[] = {'H','e','l','p','_','M','e','\0'}; //Works
help is an array, and the initializer is a list of values for the elements of the array.
// char *help = "Help_Me"; //Works
help is a pointer, and "Help_Me" is a string literal. Because it is not in one of the exceptions—operand of sizeof, operand of unary &, or used to initialize an array—it is automatically converted to a pointer to its first element. Then help is initialized with that pointer value.
char *help = {'H','e','l','p','_','M','e','\0'}; //Error
help is a pointer, but the initializer is a list of values. There is only one thing to be initialized, a pointer, but there are multiple values listed for it, so that is an error. Also, a pointer should be initialized with a pointer value (an address or a null pointer constant), but the items in that list are integers. (Character literals are integers; their values are the codes for the characters.)
{'H','e','l','p','_','M','e','\0'} is not a syntax that creates a string or an array. It is a syntax that can be used to provide a list of values when initializing an object. So the compiler does not recognize it as a string or array and does not use it to initialize the pointer help.
Pointer is not the array and it cant be initialized like an array. You need to create an object, then you can assign its reference to the pointer.
char *help = (char[]){'H','e','l','p','_','M','e','\0'};
Why does this work:
char *name = "steven";
but this doesn't:
char **names = {"steven", "randy", "ben"};
Or, why does this work:
char *names[] = {"steven", "randy", "ben"};
but, again, this doesn't:
char **names = {"steven", "randy", "ben"};
A char **p is not a 2D array, it is a pointer to a pointer to a character. However, you can have more pointers and more characters following, resembling a kind of model of a 2D structure of characters.
C compiler interpret { "steven" } as a 1D array of characters, because the braces are optional (standard chapter 6.7.9 paragraph 14).
As you tried, you can declare an array of pointers to a character by char *p[].
But if you want to have that pointer (to pointers to characters), you need to tell your compiler. The address of an array can be assigned to the pointer.
char **p = (char *[]){ "steven", "randy", "ben", };
Additional note: Since string literals are unmutable, you better add a const for the characters. And since the address of these unnamed string literals are constant, too, you can provide another one.
const char * const *p = (const char * const []){ "steven", "randy", "ben", };
I also wondered, what if I could answer you in the simplest way possible.
Why are you confused?
A simple pointer to integer for example allocated with 8 cells, acts in the same way as an array has a dimension of 8 cells.
The only difference, that you can't see, is that a pointer that has 8 cells allocated is on a part of the memory that is called the HEAP, while a variable of type int tab[8] is allocated on the STACK.
Indeed, since the cells are linked in memory, it is easy to imagine that a pointer and an array whose first cell address is sent are the same thing.
Why it doesn't work in the other case
However, when the idea comes to associate (** and [][])
Let's take the example of an int ** ;
int **tab;
tab = malloc(sizeof(int *) * 4);
//secure malloc do not forget
for (int i = 0; i < 4; i++)
{
tab[i] = malloc(sizeof(int) * 3);
//secure malloc do not forget
}
and an
int[4][3];
You have a problem.
To imagine, a double array type follows itself in memory, because it is the very principle of arrays.
While a double pointer has first 4 cells of type int * allocated (which follow each other in memory) and then each pointer of these 4 cells, each points to a memory area of 3 ints which follow each other. But the whole thing does not follow each other in the memory!
A way that may interest you
One thing you can do instead is to create an int ptr(*)[3];
which can point to the first element of an array of size 3, i.e. the address of an array [4][3] for example.
The initializer for a scalar object may not contain more than one item.
6.7.9 Initialization
...
Constraints
2 No initializer shall attempt to provide a value for an object not contained within the entity
being initialized.
...
11 The initializer for a scalar shall be a single expression, optionally enclosed in braces. The
initial value of the object is that of the expression (after conversion); the same type
constraints and conversions as for simple assignment apply, taking the type of the scalar
to be the unqualified version of its declared type
C 2011 Online Draft
char **names declares a single, scalar object, not an array, so any initializer for it must only contain a single item. That initializer may be a single string ("steven"), optionally enclosed in braces ({ "steven" }). However, it may not be a list of initializers.
#include <stdio.h>
main()
{
char * ptr;
ptr = "hello";
printf("%p %s" ,"hello",ptr );
getchar();
}
Hi, I am trying to understand clearly how can arrays get assign in to pointers. I notice when you assign an array of chars to a pointer of chars ptr="hello"; the array decays to the pointer, but in this case I am assigning a char of arrays that are not inside a variable and not a variable containing them ", does this way of assignment take a memory address specially for "Hello" (what obviously is happening) , and is it possible to modify the value of each element in "Hello" wich are contained in the memory address where this array is stored. As a comparison, is it fine for me to assign a pointer with an array for example of ints something as vague as thisint_ptr = 5,3,4,3; and the values 5,3,4,3 get located in a memory address as "Hello" did. And if not why is it possible only with strings? Thanks in advanced.
"hello" is a string literal. It is a nameless non-modifiable object of type char [6]. It is an array, and it behaves the same way any other array does. The fact that it is nameless does not really change anything. You can use it with [] operator for example, as in "hello"[3] and so on. Just like any other array, it can and will decay to pointer in most contexts.
You cannot modify the contents of a string literal because it is non-modifiable by definition. It can be physically stored in read-only memory. It can overlap other string literals, if they contain common sub-sequences of characters.
Similar functionality exists for other array types through compound literal syntax
int *p = (int []) { 1, 2, 3, 4, 5 };
In this case the right-hand side is a nameless object of type int [5], which decays to int * pointer. Compound literals are modifiable though, meaning that you can do p[3] = 8 and thus replace 4 with 8.
You can also use compound literal syntax with char arrays and do
char *p = (char []) { "hello" };
In this case the right-hand side is a modifiable nameless object of type char [6].
The first thing you should do is read section 6 of the comp.lang.c FAQ.
The string literal "hello" is an expression of type char[6] (5 characters for "hello" plus one for the terminating '\0'). It refers to an anonymous array object with static storage duration, initialized at program startup to contain those 6 character values.
In most contexts, an expression of array type is implicitly converted a pointer to the first element of the array; the exceptions are:
When it's the argument of sizeof (sizeof "hello" yields 6, not the size of a pointer);
When it's the argument of _Alignof (a new feature in C11);
When it's the argument of unary & (&arr yields the address of the entire array, not of its first element; same memory location, different type); and
When it's a string literal in an initializer used to initialize an array object (char s[6] = "hello"; copies the whole array, not just a pointer).
None of these exceptions apply to your code:
char *ptr;
ptr = "hello";
So the expression "hello" is converted to ("decays" to) a pointer to the first element ('h') of that anonymous array object I mentioned above.
So *ptr == 'h', and you can advance ptr through memory to access the other characters: 'e', 'l', 'l', 'o', and '\0'. This is what printf() does when you give it a "%s" format.
That anonymous array object, associated with the string literal, is read-only, but not const. What that means is that any attempt to modify that array, or any of its elements, has undefined behavior (because the standard explicitly says so) -- but the compiler won't necessarily warn you about it. (C++ makes string literals const; doing the same thing in C would have broken existing code that was written before const was added to the language.) So no, you can't modify the elements of "hello" -- or at least you shouldn't try. And to make the compiler warn you if you try, you should declare the pointer as const:
const char *ptr; /* pointer to const char, not const pointer to char */
ptr = "hello";
(gcc has an option, -Wwrite-strings, that causes it to treat string literals as const. This will cause it to warn about some C code that's legal as far as the standard is concerned, but such code should probably be modified to use const.)
#include <stdio.h>
main()
{
char * ptr;
ptr = "hello";
//instead of above tow lines you can write char *ptr = "hello"
printf("%p %s" ,"hello",ptr );
getchar();
}
Here you have assigned string literal "hello" to ptr it means string literal is stored in read only memory so you can't modify it. If you declare char ptr[] = "hello";, then you can modify the array.
Say what?
Your code allocates 6 bytes of memory and initializes it with the values 'h', 'e', 'l', 'l', 'o', and '\0'.
It then allocates a pointer (number of bytes for the pointer depends on implementation) and sets the pointer's value to the start of the 5 bytes mentioned previously.
You can modify the values of an array using syntax such as ptr[1] = 'a'.
Syntactically, strings are a special case. Since C doesn't have a specific string type to speak of, it does offer some shortcuts to declaring them and such. But you can easily create the same type of structure as you did for a string using int, even if the syntax must be a bit different.
I am trying to learn the basics, I would think that declaring a char[] and assigning a string to it would work.
thanks
int size = 100;
char str[size];
str = "\x80\xbb\x00\xcd";
gives error "incompatible types in assignment". what's wrong?
thanks
You can use a string literal to initialize an array of char, but you can't assign an array of char (any more than you can assign any other array). OTOH, you can assign a pointer, so the following would be allowed:
char *str;
str = "\x80\xbb\x00\xcd";
This is actually one of the most difficult parts of learning a programming language.... str is an array, that is, a part of memory (size times a char, so size chars) that has been reserved and labeled as str. str[0] is the first character, str[1] the second... str[size-1] is the last one. str itself, without specifiying any character, is a pointer to the memory zone that was created when you did
char str[size]
As Jerry so clearly said, in C you can not initialize arrays that way. You need to copy from one array to other, so you can do something like this
strncpy(str, "\x80\xbb\x00\xcd", size); /* Copy up to size characters */
str[size-1]='\0'; /* Make sure that the string is null terminated for small values of size */
Summarizing: It's very important to make a difference between pointers, memory areas and array.
Good luck - I am pretty sure that in less time than you imagine you will be mastering these concepts :)
A char-array can be implicitely cast to a char* when used as Rvalue, but not when used as Lvalue - that's why the assignment won't work.
You cannot assign array contents using the =operator. That's just a fact of the C language design. You can initialize an array in the declaration, such as
char str[size] = "\x80\xbb\x00\xcd";
but that's a different operation from an assignment. And note that in this case, and extra '\0' will be added to the end of the string.
The "incompatible types" warning comes from how array expressions are treated by the language. First of all, string literals are stored as arrays of char with static extent (meaning they exist over the lifetime of the program). So the type of the string literal "\x80\xbb\x00\xcd" is "4 5-element array of char". However, in most circumstances, an expression of array type will implicitly be converted ("decay") from type "N-element array of T" to "pointer to T", and the value of the expression will be the address of the first element in the array. So, when you wrote the statement
str = "\x80\xbb\x00\xcd";
the type of the literal was implicitly converted from "4 5-element array of char" to "pointer to char", but the target of the assignment is type "100-element array of char", and the types are not compatible (above and beyond the fact that an array expression cannot be the target of the = operator).
To copy the contents of one array to another you would have to use a library function like memcpy, memmove, strcpy, etc. Also, for strcpy to function properly, the source string must be 0-terminated.
Edit per R's comment below, I've struck out the more dumbass sections of my answer.
To assign a String Literal to the str Array you can use a the String copy function strcpy.
char a[100] = "\x80\xbb\x00\xcd"; OR char a[] = "\x80\xbb\x00\xcd";
str is the name of an array. The name of an array is the address of the 0th element. Therefore, str is a pointer constant. You cannot change the value of a pointer constant, just like you cannot change a constant (you can't do 6 = 5, for example).
I have more than one doubt so please bear with me.
Can someone tell me why this code fails?
#include<stdio.h>
void main(int argc,char **argv) /*assume program called with arguments aaa bbb ccc*/
{
char **list={"aaa","bbb","ccc"};
printf("%s",argv[1]);/*prints aaa*/
printf("%s",list[1]); /*fails*/
}
I assumed it had something to do with the pointer to pointer stuff, which i do not understand clearly. So i tried:
#include<stdio.h>
void main()
{
char **list={"aaa","bbb","ccc"};
char *ptr;
ptr=list;
printf("%s",ptr);/*this prints the first string aaa*/
/* My second question is how do i increment the value
of ptr so that it points to the second string bbb*/
}
What is the difference between char *list[] and char **list and in what situations are both ideal to be used?
One more thing confusing me is argv special? when i pass char **list to another function assuming it would let me access the contents the way i could with argv, it also failed.
I realize similar questions have been asked in the past, but i cant seem to find what i need. if so can someone please post the relevant links.
You should use char *list[]={"aaa","bbb","ccc"}; instead of char **list={"aaa","bbb","ccc"};. You use char* list[] = {...}; to declare the array of pointers, but you use char** to pass a pointer to one or more pointers to a function.
T* x[] = array of pointers
T** x = pointer to pointer
P.S. Responding to ejohn: There is only one use that I can think of for creating a pointer to a pointer (as an actual declared variable, not as a function parameter or temporary created by the unary & operator): a handle. In short, a handle is a pointer to a pointer, where the handl;e is owned by the user but the pointer it points to can be changed as needed by the OS or a library.
Handles were used extensively throughout the old Mac OS. Since Mac OS was developed without virtual memory technology, the only way to keep the heap from quickly getting fragmented was to use handles in almost all memory allocations. This let the OS move memory as needed to compact the heap and open up larger, contiguous blocks of free memory.
Truth is, this strategy at best just "sucked less". There are a huge list of disadvantages:
A common bug was when programmers would dereference the handle to a pointer, and use that pointer for several function calls. If any of those function calls moved memory, there was a chance that the pointer would become invalid, and dereferencing it would corrupt memory and possibly crash the program. This is an insidious bug, since dereferencing the bad pointer would not result in a bus error or segmentation fault, since the memory itself was still existent and accessible; it just was no longer used by the object you were using.
For this reason, the compiler had to be extra careful and some Common Subexpression Elimination optimizations couldn't be taken (the common subexpression being the handle dereference to a pointer).
So, in order to ensure proper execution, almost all accesses through handles require two indirect accesses, instead of one with a plain old pointer. This can hurt performance.
Every API provided by the OS or any library had to specify whether it could possibly "move memory". If you called one of these functions, all your pointers obtained via handles were now invalid. There wasn't a way to have the IDE do this for you or check you, since the moves-memory call and the pointer that became invalid might not even be in the same source file.
Performance becomes nondeterministic, because you never know when the OS will pause to compact your memory (which involved a lot of memcpy() work).
Multithreading becomes difficult because one thread could move memory while another is executing or blocked, invalidating its pointers. Remember, handles have to be used for almost all memory allocation to keep from fragmenting the heap, so threads are still likely to need access to memory via a handle even if they use none of the Mac OS APIs.
There were function calls for locking and unlocking the pointers pointed to by handles, however, too much locking hurts performance and fragments the heap.
There's probably several more that I forgot. Remember, all these disadvantages were still more palatable than using only pointers and quickly fragmenting the heap, especially on the first Macs, which only had 128K of RAM. This also gives some insight into why Apple was perfectly happy to ditch all this and go to BSD then they had the chance, once their entire product line had memory management units.
First of all, let's get the nitpicky stuff out of the way. main returns int, not void. Unless your compiler documentation specifically states that it supports void main(), use int main(void) or int main(int argc, char **argv).
Now let's step back a minute and talk about the differences between pointers and arrays. The first thing to remember is that arrays and pointers are completely different things. You may have heard or read somewhere that an array is just a pointer; this is incorrect. Under most circumstances, an array expression will have its type implicitly converted from "N-element array of T" to "pointer to T" (the type decays to a pointer type) and its value set to point to the first thing in the array, the exceptions being when the array expression is an operand of either the sizeof or address-of (&) operators, or when the array expression is a string literal being used to initialize another array.
An array is a block of memory sized to hold N elements of type T; a pointer is a block of memory sized to hold the address of a single value of type T. You cannot assign a new value to an array object; i.e., the following is not allowed:
int a[10], b[10];
a = b;
Note that a string literal (such as "aaa") is also an array expression; the type is N-element array of char (const char in C++), where N is the length of the string plus the terminating 0. String literals have static extent; they are allocated at program startup and exist until the program exits. They are also unwritable (attempting to modify the contents of a string literal results in undefined behavior). For example, the type of the expression "aaa" is 4-element array of char with static extent. Like other array expressions, string literals decay from array types to a pointer types in most circumstances. When you write something like
char *p = "aaa";
the array expression "aaa" decays from char [4] to char *, and its value is the address of the first 'a' of the array; that address is then copied to p.
If the literal is being used to initialize an array of char, however:
char a[] = "aaa";
then the type is not converted; the literal is still treated as an array, and the contents of the array are copied to a (and a is implicitly sized to hold the string contents plus the 0 terminator). The result is roughly equivalent to writing
char a[4];
strcpy(a, "aaa");
When an array expression of type T a[N] is the operand of the sizeof operator, the result is the size of the entire array in bytes: N * sizeof(T). When it's the operand of the address-of (&) operator, the result is a pointer to the entire array, not a pointer to the first element (in practice, these are the same value, but the types are different):
Declaration: T a[N];
Expression Type "Decays" to Value
---------- ---- ----------- ------
a T [N] T * address of a[0]
&a T (*)[N] address of a
sizeof a size_t number of bytes in a
(N * sizeof(T))
a[i] T value of a[i]
&a[i] T * address of a[i]
sizeof a[i] size_t number of bytes in a[i] (sizeof (T))
Note that the array expression a decays to type T *, or pointer to T. This is the same type as the expression &a[0]. Both of these expressions yield the address of the first element in the array. The expression &a is of type T (*)[N], or pointer to N-element array of T, and it yields the address of the array itself, not the first element. Since the address of the array is the same as the address of the first element of the array, a, &a, and &a[0] all yield the same value, but the expressions are not all the same type. This will matter when trying to match up function definitions to function calls. If you want to pass an array as a parameter to a function, like
int a[10];
...
foo(a);
then the corresponding function definition must be
void foo(int *p) { ... }
What foo receives is a pointer to int, not an array of int. Note that you can call it as either foo(a) or foo(&a[0]) (or even foo(&v), where v is a simple int variable, although if foo is expecting an array that will cause problems). Note that in the context of a function parameter declaration, int a[] is the same as int *a, but that's only true in this context. Frankly, I think the int a[] form is responsible for a lot of confused thinking about pointers, arrays, and functions, and its use should be discouraged.
If you want to pass a pointer to an array to a function, such as
int a[10];
foo(&a);
then the corresponding function definition must be
void foo(int (*p)[10]) {...}
and when you want to reference a specific element, you must dereference the pointer before applying the subscript:
for (i = 0; i < 10; i++)
(*p)[i] = i * i;
Now let's throw a monkey wrench into the works and add a second dimension to the array:
Declaration: T a[M][N];
Expression Type "Decays" to Value
---------- ---- ----------- ------
a T [M][N] T (*)[N] address of a[0]
&a T (*)[M][N] address of a
sizeof a size_t number of bytes in a (M * N * sizeof(T))
a[i] T [N] T * address of a[i][0]
&a[i] T (*)[N] address of a[i]
sizeof a[i] size_t number of bytes in a[i] (N * sizeof(T))
a[i][j] T value of a[i][j]
&a[i][j] T * address of a[i][j]
Note that in this case, both a and a[i] are array expressions, so their respective array types will decay to pointer types in most circumstances; a will be converted from type "M-element array of N-element array of T" to "pointer to N-element array of T", and a[i] will be converted from "N-element array of T" to "pointer to T". And again, a, &a, a[0], &a[0], and &a[0][0] will all yield the same values (the address of the beginning of the array), but not be all the same types. If you want to pass a 2d array to a function, like:
int a[10][20];
foo(a);
then the corresponding function definition must be
void foo(int (*p)[20]) {...}
Notice that this is identical to passing a pointer to a 1-d array (other than the size of the array in the examples being different). In this case, however, you would apply a subscript to the pointer, like
for (i = 0; i < 10; i++)
for (j = 0; j < 20; j++)
p[i][j] = i * j;
You don't have to explicitly dereference p in this case, because the expression p[i] implicitly deferences it (p[i] == *(p + i)).
Now let's look at pointer expressions:
Declaration: T *p;
Expression Type Value
---------- ---- ------
p T * address of another object of type T
*p T value of another object of type T
&p T ** address of the pointer
sizeof p size_t number of bytes in pointer (depends on type and platform,
anywhere between 4 and 8 on common desktop architectures)
sizeof *p size_t number of bytes in T
sizeof &p size_t number of bytes in pointer to pointer (again, depends
on type and platform)
This is all pretty straightforward. A pointer type holds the address of another object of type T; dereferencing the pointer (*p) yields the value at that address, and taking the address of the pointer (&p) yields the location of the pointer object (a pointer to a pointer). Applying sizeof to a pointer value will yield the number of bytes in the pointer, not the number of bytes in what the pointer is pointing to.
Now, assuming you've made it this far and haven't yet died of ennui, let's see how all of that applies to your code.
You're wanting to create an array of pointers to char and initialize it with three string literals, so you would declare it as
char *list[] = {"aaa", "bbb", "ccc"};
The list array is implicitly sized to hold 3 elements of type char *. Even though the string literals "aaa", "bbb", and "ccc" appear in an initializer, they are not being used to initialize an array of char; therefore, they decay from expressions of type char [4] to type char *. Each of these pointer values is copied to the elements of list.
When you pass list to a function, such as
foo(list);
the type of list decays from "4-element array of pointer to char" (char *[4]) to "pointer to pointer to char" (char **), so the receiving function must have a definition of
void foo(char **p) {...}
Since subscripting is defined in terms of pointer arithmetic, you can use the subscript operator on the pointer as though it were an array of char *:
for (i = 0; i < 3; i++)
printf("%s\n", p[i]);
Incidentally, this is how main receives argv, as a pointer to pointer to char (char **), not as an array of pointer to char. Remember, in terms of a function parameter declaration, a[] is identical to *a, so char *argv[] is identical to char **argv.
Now, because I can't seem to stop typing and get back to work (chasing down deadlocks is not fun), let's explore using pointers and dynamically allocated memory.
If you wanted to allocate your list dynamically at run time (i.e., you won't know how many strings are in your list ahead of time), you would declare list as a pointer to pointer to char, and then call malloc to actually allocate the memory for it:
char **list;
size_t number_of_strings;
...
list = malloc(number_of_strings * sizeof *list);
list[0] = "aaa";
list[1] = "bbb";
list[2] = "ccc";
...
Since these are assignments and not initializations, the literal expressions decay into pointers to char, so we're copying the addresses of "aaa", "bbb", etc., to the entries in list. In this case, list is not an array type; it is simply a pointer to a chunk of memory allocated somewhere else (in this case, from the malloc heap). Again, since array subscripting is defined in terms of pointer arithmetic, you can apply the subscript operator to a pointer value as though it were an array. The type of the expression list[i] is char *. There are no implicit conversions to worry about; if you pass it to a function as
foo(list)
then the function definition would be
void foo(char **list) {...}
and you would subscript list as though it were an array.
pssst...is he done?
Yeah, I think he's done.
char **x points to an array of char pointers, however this may not be how your compiler stores {"aaa","bbb","ccc"} in memory. char *x[] will cause the correct code to be generated no matter how the compiler stores an array of pointers.
The best source for learning the complexities of C is the book Expert C Programming by Peter van der Linden (http://www.amazon.co.uk/Expert-Programming-Peter-van-Linden/dp/0131774298).
The name of the book is misleading because it's very easily read by beginners I think.
"...assumed it had something to do
with the pointer to pointer stuff,
which i do not understand clearly."
How does an array of pointers to pointers work?