82
int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);

causes a segfault, whereas

int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);

doesn't. Now:

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);

prints 5.

Based on this, I have conjectured that the array initialization notation, {}, blindly loads this data into whatever variable is on the left. When it is int[], the array is filled up as desired. When it is int*, the pointer is filled up by 5, and the memory locations after where the pointer is stored are filled up by 2, 1, and 4. So nums[0] attempts to deref 5, causing a segfault.

If I'm wrong, please correct me. And if I'm correct, please elaborate, because I don't understand why array initializers work the way they do.

artm
  • 17,291
  • 6
  • 38
  • 54
user1299784
  • 2,019
  • 18
  • 30
  • 7
    possible duplicate of [Initializing “a pointer to an array of integers”](http://stackoverflow.com/q/17850998/11683) – GSerg Feb 08 '16 at 10:17
  • I think the crux (from @GSerg's reference, last answer) is that `{5,2,1,4}` is an _initializer list_, not an array. So after `int *nums ={5,2,1,4};` you are not pointing to an array, which is what you wanted to do. – Paul Ogilvie Feb 08 '16 at 10:23
  • 3
    Compile with all warnings enabled and your compiler should tell you what happens. – Jabberwocky Feb 08 '16 at 10:45
  • 1
    @GSerg That's not anywhere near a duplicate. There's no array pointer in this question. Even though some answers in that post are similar to those here. – Lundin Feb 08 '16 at 10:46
  • 2
    @Lundin I was 30% sure so I did not vote to close, only posted the link. – GSerg Feb 08 '16 at 14:55
  • `printf("%d\n", nums);` also uses the wrong format specifier (causing undefined behaviour) – M.M Feb 08 '16 at 20:40
  • 3
    Get a habit of running GCC with `-pedantic-errors` flag and watch the diagnostic. `int *nums = {5, 2, 1, 4};` is not valid C. – AnT stands with Russia Feb 08 '16 at 23:07

5 Answers5

116

There is a (stupid) rule in C saying that any plain variable may be initialized with a brace-enclosed initializer list, just as if it was an array.

For example you can write int x = {0};, which is completely equivalent to int x = 0;.

So when you write int *nums = {5, 2, 1, 4}; you are actually giving an initializer list to a single pointer variable. However, it is just one single variable so it will only get assigned the first value 5, the rest of the list is ignored (actually I don't think that code with excess initializers should even compile with a strict compiler) - it does not get written to memory at all. The code is equivalent to int *nums = 5;. Which means, numsshould point at address 5.

At this point you should already have gotten two compiler warnings/errors:

  • Assigning integer to pointer without a cast.
  • Excess elements in initializer list.

And then of course the code will crash and burn since 5 is most likely not a valid address you are allowed to dereference with nums[0].

As a side note, you should printf pointer addresses with the %p specifier or otherwise you are invoking undefined behavior.


I'm not quite sure what you are trying to do here, but if you want to set a pointer to point at an array, you should do:

int nums[] = {5, 2, 1, 4};
int* ptr = nums;

// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};

Or if you want to create an array of pointers:

int* ptr[] = { /* whatever makes sense here */ };

EDIT

After some research I can say that the "excess elements initializer list" is indeed not valid C - it is a GCC extension.

The standard 6.7.9 Initialization says (emphasis mine):

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.

"Scalar type" is a standard term referring to single variables that are not of array, struct or union type (those are called "aggregate type").

So in plain English the standard says: "when you initialize a variable, feel free to toss in some extra braces around the initializer expression, just because you can."

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Is the code really legal standard C (syntactically)? Another answer is saying that the initializer list is a GCC extension. Can you give a citation? – Nate Eldredge Feb 08 '16 at 15:50
  • @NateEldredge Yes I believe the answer by M.M is correct in saying that the excess initializer list is some GCC extension and shouldn't compile with a strict compiler. I don't remember this off the top of my head, let me check the standard. – Lundin Feb 08 '16 at 15:53
  • Doesn't the assignment itself, i.e, `int *nums = {5, 2, 1, 4};` invoke Undefined Behavior? – Spikatrix Feb 08 '16 at 16:09
  • @CoolGuy Turns out this have already been debated before, [here](http://stackoverflow.com/questions/26627863/array-initialisation-in-c). – Lundin Feb 08 '16 at 16:19
  • Edited the post with a bit of research. – Lundin Feb 08 '16 at 16:20
  • 11
    There's nothing "stupid" about the ability to initialize a scalar object with a single value enclosed in `{}`. On the contrary, it facilitates one of the most important and convenient idioms of C language - `{ 0 }` as universal zero-initializer. Everything in C can be zero-initialized through `= { 0 }`. This is very important for writing type-independent code. – AnT stands with Russia Feb 08 '16 at 23:05
  • `int *nums = 5;` is also a constraint violation: an integer may not be converted to a pointer without a cast (except for null pointer constants). There's another compiler extension in play to effectively insert a cast. Also, this conversion may cause undefined behaviour by generating a misaligned address or a trap representation. – M.M Feb 09 '16 at 00:42
  • 3
    @AnT There's no such thing as an "universal zero-initializer". In case of aggregates, `{0}` merely means initialize the first object to zero and initialize the rest of the objects as if they had static storage duration. I would say this is by coincidence rather than intentional language design of some "universal initializer", since `{1}` does not initialize all objects to 1. – Lundin Feb 09 '16 at 07:19
  • @M.M I don't think the cast is required by the C standard (unlike C++), can you cite the relevant section? Nothing in 6.3.2.3 pointer conversions mentions the need of any casts. – Lundin Feb 09 '16 at 07:22
  • 3
    @Lundin C11 6.5.16.1/1 covers `p = 5;` (none of the listed cases is met for assigning integer to pointer); and 6.7.9/11 says that the constraints for assignment are also used for initialization. – M.M Feb 09 '16 at 08:04
  • @M.M Ah right. 6.5.16.1 isn't exactly trivial reading but indeed none of those cases seem to be "left operand is pointer and right operand is arithmetic type" or vice versa. Except for some curious reason where they explicitly allow "left operand is _Bool and right operand is pointer". – Lundin Feb 09 '16 at 08:53
  • Maybe the latter is to allow things like `_Bool success = fgets(....);`, or something – M.M Feb 09 '16 at 08:58
  • 4
    @Lundin: Yes, there is. It is completely irrelevant what mechanism initializes what part of the object. It is also completely irrelevant whether `{}` initailization of scalars is allowed that purpose specifically. The only thing that matters is that `= { 0 }` initializer is guaranteed to *zero-initialize the entire object*, which is exactly what made it a classic and one of the most elegant idioms of C language. – AnT stands with Russia Feb 09 '16 at 18:04
  • 2
    @Lundin: It is also completely unclear to me what your remark about `{1}` has to do with the topic. Nobody ever claimed that `{0}` interprets that `0` as a multi-initializer for each and every member of aggregate. – AnT stands with Russia Feb 09 '16 at 18:04
  • 1
    @Lundin: `int *p = 5` is not valid C. This has been beaten to death already, but somehow this misguided belief refuses to go away. 6.3.2.3 says absolutely nothing to allow implicit integer-pointer conversions. The purpose of the entire 6.3 is to describe how conversions work *when they are legal*. But 6.3 says absolutely nothing about what implicit conversions are legal in what specific contexts. For that you have to look a different portion of the standard document - the one that describes the specific context. – AnT stands with Russia Feb 09 '16 at 18:12
  • 1
    @Lundin: In case of `int *p = 5`, you have to look the spec of *initialization*, which will redirect you to the section about *simple assignment*. And rules of simple assignment prohibit assigning integer values to pointers (with the exception of NPC, of course). – AnT stands with Russia Feb 09 '16 at 18:18
  • Isn't `int* ptr = (int[]){5, 2, 1, 4};` making `ptr` point to a temporary? – Enn Michael Feb 07 '18 at 23:52
  • @EnnMichael Just as the `nums` array is "temporary" and only valid within the scope it is declared, yes. – Lundin Feb 08 '18 at 07:24
  • @Lundin So the `{5, 2, 1, 4}` array is valid in the whole block in which `ptr` is declared? Also am I correct that in C++ the `{5, 2, 1, 4}` would only be valid for the duration of the statement `int* ptr = (int[]){5, 2, 1, 4};`? (GCC warns about this code for C++, but not for C.) – Enn Michael Feb 08 '18 at 22:38
  • 1
    @EnnMichael Yes, it has local scope like any other local variable. Where did C++ come from? C++ doesn't have compound literals, this feature is only available in C. – Lundin Feb 09 '18 at 08:01
28

SCENARIO 1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault

Why does this one segfault?

You declared nums as a pointer to int - that is nums is supposed to hold the address of one integer in the memory.

You then tried to initialize nums to an array of multiple values. So without digging into much details, this is conceptually incorrect - it does not make sense to assign multiple values to a variable that is supposed to hold one value. In this regard, you'd see exactly the same effect if you do this:

int nums = {5, 2, 1, 4};    // <-- assign multiple values to an int variable
printf("%d\n", nums);    // also print 5

In either case (assign multiple values to a pointer or an int variable), what happens then is that the variable will get the first value which is 5, while remaining values are ignored. This code complies but you would get warnings for each additional value that is not supposed to be in the assignment:

warning: excess elements in scalar initializer.

For the case of assigning multiple values to pointer variable, the program segfaults when you access nums[0], which means you are deferencing whatever is stored in address 5 literally. You did not allocate any valid memory for pointer nums in this case.

It'd be worth noting that there is no segfault for the case of assigning multiple values to int variable (you are not dereferencing any invalid pointer here).


SCENARIO 2

int nums[] = {5, 2, 1, 4};

This one does not segfault, because you are legally allocating an array of 4 ints in the stack.


SCENARIO 3

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);   // print 5

This one does not segfault as expected, because you are printing the value of the pointer itself - NOT what it's dereferencing (which is invalid memory access).


Others

It's almost always doomed to segfault whenever you hardcode the value of a pointer like this (because it is the operating system task to determine what process can access what memory location).

int *nums = 5;    // <-- segfault

So a rule of thumb is to always initialize a pointer to the address of some allocated variable, such as:

int a;
int *nums = &a;

or,

int a[] = {5, 2, 1, 4};
int *nums = a; 
Paul
  • 10,381
  • 13
  • 48
  • 86
artm
  • 17,291
  • 6
  • 38
  • 54
  • 2
    +1 That's good advice, but "never" really is too strong, given the magic addresses in many platforms. (Using constant tables for those fixed addresses is not pointing at extant variables, and so violates your rule as stated.) Low level stuff like driver development deals with that kind of thing pretty frequently. – The Nate Feb 08 '16 at 15:42
  • 3
    "This is valid" - ignoring excess initializers is a GCC extension; in Standard C that is not allowed – M.M Feb 08 '16 at 20:39
  • 1
    @TheNate - yes you are correct. I edited base on your comment - thanks. – artm Feb 08 '16 at 23:02
  • @M.M - thanks for pointing that out. I edited to remove that. – artm Feb 08 '16 at 23:15
26

int *nums = {5, 2, 1, 4}; is ill-formed code. There is a GCC extension which treats this code the same as:

int *nums = (int *)5;

attempting to form a pointer to memory address 5. (This doesn't seem like a useful extension to me, but I guess the developer base wants it).

To avoid this behaviour (or at least, get a warning) you could compile in standard mode, e.g. -std=c11 -pedantic.

An alternative form of valid code would be:

int *nums = (int[]){5, 2, 1, 4};

which points at a mutable literal of the same storage duration as nums. However , the int nums[] version is generally better as it uses less storage, and you can use sizeof to detect how long the array is.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Would the array in the compound-literal form be guaranteed to have a storage lifetime at least as long as that of `nums`? – supercat Feb 08 '16 at 19:20
  • @supercat yes, it's automatic if nums is automatic, and static if nums is static – M.M Feb 08 '16 at 20:18
  • @M.M: Would that apply even if `nums` is a static variable declared within a function, or would the compiler be entitled to limit the lifetime of the array to that of the enclosing block even if it was being assigned to a static variable? – supercat Feb 08 '16 at 20:49
  • @supercat yes (the first bit) . The second option would mean UB on the second time the function is called (since static variables are only initialized on the first call) – M.M Feb 08 '16 at 20:50
12
int *nums = {5, 2, 1, 4};

nums is a pointer of type int. So you should make this point to some valid memory location. num[0] you are trying to dereference some random memory location and hence the segmentation fault.

Yes the pointer is holding value 5 and you are trying to dereference it which is undefined behavior on your system. (Looks like 5 is not a valid memory location on your system)

Whereas

int nums[] = {1,2,3,4};

is a valid declaration where you are saying nums is an array of type int and memory is allocated based on number of elements passed during initialization.

Gopi
  • 19,784
  • 4
  • 24
  • 36
  • 1
    "Yes the pointer is holding value 5 and you are trying to dereference it which is undefined behavior." Not at all, it is perfectly fine and well-defined behavior. But on the system the OP is using, it is not a valid memory address, hence the crash. – Lundin Feb 08 '16 at 10:30
  • @Lundin Agree. But I think OP never knew 5 is a valid memory location so I spoke on those lines. Hope the edit helps – Gopi Feb 08 '16 at 10:32
  • Should be like this ? `int *nums = (int[]){5, 2, 1, 4};` – Islam Azab Feb 16 '16 at 16:12
10

By assigning {5, 2, 1, 4}

int *nums = {5, 2, 1, 4};

you are assigning 5 to nums (after an implicit typecast from int to pointer to int). Dereferring it makes an access call to memory location at 0x5. That might not be allowed for your program to access.

Try

printf("%p", (void *)nums);
Gopi
  • 19,784
  • 4
  • 24
  • 36
Fahad Siddiqui
  • 1,829
  • 1
  • 19
  • 41