C compound literals, pointer to arrays
Asked Answered
O

5

33

I'm trying to assign a compound literal to a variable, but it seems not to work, see:

  int *p[] = (int *[]) {{1,2,3},{4,5,6}};

I got a error in gcc.

but if I write only this:

  int p[] = (int []) {1,2,3,4,5,6};

Then it's okay.

But is not what I want.

I don't understand why the error occurrs, because if I initialize it like a array, or use it with a pointer of arrays of chars, its okay, see:

  int *p[] = (int *[]) {{1,2,3},{4,5,6}}; //I got a error
  int p[][3] = {{1,2,3},{4,5,6}}; //it's okay
  char *p[] = (char *[]) {"one", "two"...}; // it's okay!

Note I don't understand why I got an error in the first one, and please I can't, or I don't want to write like the second form because it's needs to be a compound literals, and I don't want to say how big is the array to the compiler. I want something like the second one, but for int values.

Thanks in advance.

Ordure answered 31/3, 2011 at 6:21 Comment(4)
Why do you need to initialize these arrays in one statement?Ringdove
If I understood the question, it simplify things, I can swap va_args to compound literals for example...Ordure
You state several times that you get an error, yet never tell us what the error is. Have you learned nothing, Padawan?Rosiarosicrucian
int p[] = (int []) {1,2,3,4,5,6}; gives some error with gcc 4.8.2 on Linux. The error is 'array initialized from non-constant array expression'. What's your gcc version?Breena
R
32

First, the casts are redundant in all of your examples and can be removed. Secondly, you are using the syntax for initializing a multidimensional array, and that requires the second dimension the be defined in order to allocate a sequential block of memory. Instead, try one of the two approaches below:

  • Multidimensional array:

    int p[][3] = {{1,2,3},{4,5,6}};
    
  • Array of pointers to one dimensional arrays:

    int p1[] = {1,2,3};
    int p2[] = {4,5,6};
    int *p[] = {p1,p2};
    

The latter method has the advantage of allowing for sub-arrays of varying length. Whereas, the former method ensures that the memory is laid out contiguously.

Another approach that I highly recommend that you do NOT use is to encode the integers in string literals. This is a non-portable hack. Also, the data in string literals is supposed to be constant. Do your arrays need to be mutable?

int *p[] = (int *[]) {
    "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00",
    "\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00"
};

That example might work on a 32-bit little-endian machine, but I'm typing this from an iPad and cannot verify it at the moment. Again, please don't use that; I feel dirty for even bringing it up.

The casting method you discovered also appears to work with a pointer to a pointer. That can be indexed like a multidimensional array as well.

int **p = (int *[]) { (int[]) {1,2,3}, (int[]) {4,5,6} };
Ringdove answered 31/3, 2011 at 6:31 Comment(10)
Yes, it's redundant in my example, but it's would good for a function parameter, and how can I avoid the dimension with pointer? like the second example... thanks for your answer.Ordure
@Ordure - you have to tell C the last array size, since the C will not try and guess on its own. It is important for pointer arithmetics etc.Diphenylhydantoin
Thanks guy, Are there some way to write the your first example like the last one? Because I can't say to compiler how big is my array, and it needs to be a array inside another....I begin to think that it is not possible...Thanks anyway.Ordure
You could encode the integers in string literals, but that would not be portable.Ringdove
Hi, this hack is nicer, see, I found an approach to get what I want... If I write this in this way : int **p = (int *[]) {{1,2,3},(int []){4,5,6}}; it works, it will help me a lot, but see, writing (int []) in every array is no so perfect because I will need to use macros to obfuscate it...maybe another solution for my problem?Ordure
The correct is : int **p = (int *[]) {(int []){1,2,3},(int []){4,5,6}};Ordure
Yeah, that seems to work. It is definitely better than using string literals.Ringdove
the type in () is not a cast -- it is a part of the compound literal syntax.Canzona
@Canzona Yes. That's what I was thinking too. I didn't trust myself atm because I'm so dead tired but I thought this cannot possibly be correct what the answer started out as. It's not a cast but indeed part of the compound literal syntax. Thanks for confirming this. Actually if I wasn't so tired I would say that this answer has the wrong idea (at least for the first part).Manchukuo
On second thought: it must be that the answer is trying to show the differences. Still your point is valid. But to Judge: compound literals isn't casting. As the other commentator points out the () is part of the compound literal syntax.Manchukuo
A
13

First understand that "Arrays are not pointers".

int p[] = (int []) {1,2,3,4,5,6};

In the above case p is an array of integers. Copying the elements {1,2,3,4,5,6} to p. Typecasting is not necessary here and both the rvalue and lvalue types match which is an integer array and so no error.

int *p[] = (int *[]) {{1,2,3},{4,5,6}};

"Note I don't understand why I got a error in the first one,.."

In the above case, p an array of integer pointers. But the {{1,2,3},{4,5,6}} is a two dimensional array ( i.e., [][] ) and cannot be type casted to array of pointers. You need to initialize as -

int p[][3] = { {1,2,3},{4,5,6} };
  // ^^ First index of array is optional because with each column having 3 elements
  // it is obvious that array has two rows which compiler can figure out.

But why did this statement compile ?

char *p[] = {"one", "two"...};

String literals are different from integer literals. In this case also, p is an array of character pointers. When actually said "one", it can either be copied to an array or point to its location considering it as read only.

char cpy[] = "one" ;
cpy[0] = 't' ;  // Not a problem

char *readOnly = "one" ;
readOnly[0] = 't' ;  // Error because of copy of it is not made but pointing
                     // to a read only location.

With string literals, either of the above case is possible. So, that is the reason the statement compiled. But -

char *p[] = {"one", "two"...}; // All the string literals are stored in 
                               // read only locations and at each of the array index 
                               // stores the starting index of each string literal.

I don't want to say how big is the array to the compiler.

Dynamically allocating the memory using malloc is the solution.

Hope it helps !

Alejandroalejo answered 31/3, 2011 at 6:54 Comment(7)
You said that {{1,2,3},{4,5,6}} is a two dimensional array , and cannot be type casted to array of ponters, but I learned that any array is a pointer, and it is passed like a pointer to functions, thanks for your answer!!!Ordure
If array is a pointer, then try this - void foo( int **twoDimensionalArray){} and try to pass p as mentioned in your example which have the okey comment.Alejandroalejo
Just because it is possible for a 1D array doesn't mean that array is a pointer.Alejandroalejo
Sorry but I can't say how big is it to compiler, much less use malloc, the perfect approach is to use pointer instead, I found a solution writing : int **p = (int *[]) {(int []){1,2,3},(int []){4,5,6}}; but I need to cast to every array inside the main array, I'm finding a solution to do without it.Ordure
There are subtle differences between arrays and pointer, but I don't think it will matter in your case other than I itialization options.Ringdove
One of differences is that you can't change the address of array, but you can do that for pointer. For that reason you can call array static address and pointer - dynamic address.Vergeboard
@Ordure No. As Judge says there are differences. Whereas int *p; isn't allocated int a[4]; is: a[0] = 1; is perfectly safe but p[1] = 1 (or *(p+1)) is not. I hope I typed that wrong (can barely see atm!). Now passing a to a function it will act as a pointer to the first element but that's not the same thing. There are other ways this shows itself.Manchukuo
T
5

Since nobody's said it: If you want to have a pointer-to-2D-array, you can (probably) do something like

int (*p)[][3] = &(int[][3]) {{1,2,3},{4,5,6}};

EDIT: Or you can have a pointer to its first element via

int (*p)[3] = (int[][3]) {{1,2,3},{4,5,6}};

The reason why your example doesn't work is because {{1,2,3},{4,5,6}} is not a valid initializer for type int*[] (because {1,2,3} is not a valid initializer for int*). Note that it is not an int[2][3] — it's simply an invalid expression.

The reason why it works for strings is because "one" is a valid initializer for char[] and char[N] (for some N>3). As an expression, it's approximately equivalent to (const char[]){'o','n','e','\0'} except the compiler doesn't complain too much when it loses constness.

And yes, there's a big difference between an initializer and an expression. I'm pretty sure char s[] = (char[]){3,2,1,0}; is a compile error in C99 (and possibly C++ pre-0x). There are loads of other things too, but T foo = ...; is variable initialization, not assignment, even though they look similar. (They are especially different in C++, since the assignment operator is not called.)

And the reason for the confusion with pointers:

  • Type T[] is implicitly converted to type T* (a pointer to its first element) when necessary.
  • T arg1[] in a function argument list actually means T * arg1. You cannot pass an array to a function for Various Reasons. It is not possible. If you try, you are actually passing a pointer-to-array. (You can, however, pass a struct containing a fixed-size array to a function.)
  • They both can be dereferenced and subscripted with identical (I think) semantics.

EDIT: The observant might notice that my first example is roughly syntactically equivalent to int * p = &1;, which is invalid. This works in C99 because a compound literal inside a function "has automatic storage duration associated with the enclosing block" (ISO/IEC 9899:TC3).

Tonyatonye answered 12/4, 2011 at 0:52 Comment(2)
Could you add some example where T[] is implicitly cvonverted to T(*)[]? You explanation is thorough! I think your anwer really see through the mist of compound literal and points out the true problem!Amaras
After some test, I think it should be int (*p)[3] = (int[][3]) {{1,2,3},{4,5,6}};Amaras
J
1

The one that you are using is array of int pointers. You should use pointer to array :

int (*p)[] = (int *) {{1,2,3}, {4,5,6}}

Look at this answer for more details.

Junket answered 8/4, 2011 at 7:15 Comment(6)
I don't think that even compiles.Tonyatonye
@tc Check this answer #860134 and you should try it first before commenting.Junket
I'm still pretty sure it does not compile.Tonyatonye
Here is the code: #include <stdio.h> void main() { int (*p)[] = (int *) {{1,2,3},{4,5,6}}; }Junket
I was wrong; it appears to compile, but is equivalent to int (*p)[] = (int*)1; ("braces around scalar initializer", "initialization makes pointer from integer without a cast", "excess elements in scalar initializer", "initialization from incompatible pointer type", "return type from 'main' is not 'int'"). I stand by my downvote.Tonyatonye
@Junket Maybe. But your example is undefined. In other words the compiler is free to do whatever it wants even if that's halt and catch fire (en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing) ). main() must return an int. This is not negotiable. Even if it appears to work with your compiler it's wrong.Manchukuo
Z
1

It seems you are confusing pointers and array. They're not the same thing! An array is the list itself, while a pointer is just an address. Then, with pointer arithmetic you can pretend pointers are array, and with the fact that the name of an array is a pointer to the first element everything sums up in a mess. ;)

int *p[] = (int *[]) {{1,2,3},{4,5,6}};      //I got a error

Here, p is an array of pointers, so you are trying to assign the elements whose addresses are 1, 2, 3 to the first array and 4, 5, 6 to the second array. The seg fault happens because you can't access those memory locations.

int p[][3] = {{1,2,3},{4,5,6}};              //it's okay

This is ok because this is an array of arrays, so this time 1, 2, 3, 4, 5 and 6 aren't addresses but the elements themselves.

char *p[] = (char *[]) {"one", "two"...};    // it's okay!

This is ok because the string literals ("one", "two", ...) aren't really strings but pointers to those strings, so you're assigning to p[1] the address of the string literal "one".
BTW, this is the same as doing char abc[]; abc = "abc";. This won't compile, because you can't assign a pointer to an array, while char *def; def = "def"; solves the problem.

Zygophyte answered 8/4, 2011 at 12:25 Comment(1)
Um, char abc[] = "abc"; compiles just fine -- string literals are valid initializers for char arrays.Keare

© 2022 - 2024 — McMap. All rights reserved.