2

I have a nested struct in C99 (I'm using GCC 4.8.3 with -std=gnu99 -Wall).

struct X
{
  struct
  {
    int p;
    int q;
  }
  a;

  struct
  {
    int m;
    int n;
  }
  b;

  int c, d, e;
};

I want to define a "default value" for it which is all-zeroes. One way would be to explicitly specify the value of each and every field, but I'd like to use the shortcut { 0 } as a default value. This is known as the "universal zero-initializer" - assigning it to a variable will zero all fields of that variable.

However, if I try this:

struct X x = { 0 };

I get warning: missing braces around initializer, then further warnings about missing initializers for fields of X.

Generally, zeroing x is not a problem. I am aware of other options such as memset() and using automatic initialization of a static variable to all-zeroes. This question is about the universal zero-initializer, and why it generates warnings unexpectedly.

Why does the above generate warnings, when it seems like it should be fine?

  • 2
    If you want to assign zeros, try `memset(&variable, 0, sizeof variable);` – pmg Jun 02 '15 at 12:12
  • Yes `memset()` allows you to change the struct without the need to modify `memset()`. – Iharob Al Asimi Jun 02 '15 at 12:13
  • @pmg Thanks, but you'll see from my question that I've considered that and it doesn't suit. – Rattus Ex Machina Jun 02 '15 at 12:13
  • @pmg sir, you're very right, but isn't it like 1) OP already knows this and 2) he wants only a paricular _nested_ member to be initalized? – Sourav Ghosh Jun 02 '15 at 12:14
  • `#define X_DEFAULT(var) memset(&var, 0, sizeof var)` – pmg Jun 02 '15 at 12:15
  • AFAIK what you're doing is actually perfectly fine. The compiler warnings are paranoid in this case. – EOF Jun 02 '15 at 12:16
  • What do you mean by "*(a) an explicit X_DEFAULT*"? – Eregrith Jun 02 '15 at 12:17
  • @Eregrith I mean that I want to have an explicit default value, in the header alongside the declaration of X, rather than the assumed default value of all-zero that would be implied by the use of memset() in the source file. Because, though the current default value is all-zero, it may not be in the future, and this assignment could easily not get updated if that changed, and there was not a connection of this sort (there may be many such assignments in the source code, in practice). – Rattus Ex Machina Jun 02 '15 at 12:20
  • Since you are using GCC, you can try just `{}` without the zero. It's a GCC extension that should do exactly what you need. – mtijanic Jun 02 '15 at 12:22
  • @mtijanic Yes, I thought that too - only, it doesn't work, I still get `missing initializer for field`. – Rattus Ex Machina Jun 02 '15 at 12:24
  • n1570 (C11 draft standard), Section 6.7.9, §19: `[...] all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.` – EOF Jun 02 '15 at 12:25
  • Rightly or wrongly, using X_DEFAULT works in Microsoft C on any structure without having to cast it. – cup Jun 02 '15 at 12:26
  • @EOF I agree, the compiler is doubtless doing the right thing, despite the warnings. But the presence of the warnings suggests there may be a more correct way to do this. In any case, I don't want to have to start disabling individual warnings, that's always going to be a maintenance pain. – Rattus Ex Machina Jun 02 '15 at 12:26
  • @cup Yeah, it does work in MS C, I know. But today, I'm using GCC. – Rattus Ex Machina Jun 02 '15 at 12:27
  • @RattusExMachina: It may not even be possible. I've had `missing braces around initializer` warnings from gcc on code that used POSIX-standard library types (`struct sigaction`? Something like that), because they contain a union as a member, but that is opaque implementation I *shouldn't* touch. – EOF Jun 02 '15 at 12:30
  • Tried X_DEFAULT without the cast on gcc 4.8.2. That also works. – cup Jun 02 '15 at 12:34
  • @cup I'm using gcc 4.8.3 (actually, `arm-none-eabi-gcc` 4.8.3). Are you in C99 mode? – Rattus Ex Machina Jun 02 '15 at 12:37
  • Yes -std=c99 on an x86 Linux machine. It doesn't work if you compile with -Wall or with -Wmissing-braces – cup Jun 02 '15 at 12:47
  • @cup That's it then, I'm using `-Wall`. I've updated the question to indicate this. – Rattus Ex Machina Jun 02 '15 at 12:48
  • @RattusExMachina The code IS fine, it's just that the gcc developers decided to issue a warning here, to remind people that not all fields are explicitly initialized. However, = {0}; will initialize everything to 0, despite the warning(You may have two warnings here, `= {{0}}` would be more proper since the 1. member is itself a struct). So you can: a) use another compiler. b) disable this specific warning in gcc c) initialize it yourself with memset(). d) spell out all the members in the initializer. – nos Jun 02 '15 at 14:15

4 Answers4

2

(I'm going to answer this myself, based on what I've learned from others' contributions.)

Short Answer

In short, this is a GCC bug which is fixed by version 5.1:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119

Workaround for earlier versions

This problem does not occur when assigning to non-nested structures (structures that contain only built-in types). Therefore, a usable workaround is to make an assignment to any field of the parent structure that is valid without warnings in GCC 4.8.3. Remaining fields will then be zeroed, as per the standard. For instance:

struct X x = { .a = { 0 } };

See also Why is the compiler throwing this warning: "missing initializer"? Isn't the structure initialized?

  • I'm not sure if this is a duplicate or not. I mean, it's not, in that it's specific to version < 5. And it's not, in that it discusses the problem in a way I understand. But it's clearly an issue that has come up many times... – Rattus Ex Machina Jun 02 '15 at 13:32
1

First, memset and {0} achieve different things. memset will set each byte of the struct to 0 in this case, so including padding bytes because sizeof will include them as well.
{0} will initialize each member to 0 because you're not initializing every member so the remaining will automatically be zero initialized. This behavior is perfectly valid and standard C.

If gcc is too restrictive about that, consider adding -Wno-missing-initializer although GCC 5.1 seems to have let up on that.

edmz
  • 8,220
  • 2
  • 26
  • 45
  • Thanks. I have two problems with that, though. Well, one problem... (a) I like that warning, for all other instances, so I don't want to disable it. And, (b), which field (this is more curiosity than a problem) gets the explicit `0` value I provided, and which are set to the as-per-static values? Apart from (a), that at least feels like a formal solution. – Rattus Ex Machina Jun 02 '15 at 12:57
  • Ah, sorry, I misunderstood the second part of your answer. Looking at your `seems`, it looks like 5.1 can handle this well without the `-Wno` flag, right? I doubt I can upgrade my compiler, but "this is fixed in a later version" would be a formal solution to the original question. Is that what's going on, here? – Rattus Ex Machina Jun 02 '15 at 13:00
  • Can you clarify "(b)"? About your second question. As I've said, it's not your fault and neither is compiler's, totally: it's just a warning and it's not there by default. As far as I've read, in addition, it's displayed because there was/is no distinction between the general case and actual zero-initialization. – edmz Jun 02 '15 at 13:24
  • I'm not sure how to finalise this - your answer was instructive, and put me on the right track, but I've tried to be more explicit in the answer I provided myself about the things that confused me. Not sure how to assign credit, nor whether to delete as a duplicate, even. – Rattus Ex Machina Jun 02 '15 at 13:33
  • It's perfectly fine to answer your question. However, from the question, it's pretty confusing what you really want to achieve; you'd better clarify it and omit some irrelevant details. – edmz Jun 02 '15 at 13:50
  • I've tried to hone both question and answer to get to what the original problem was. Hopefully got there. – Rattus Ex Machina Jun 02 '15 at 14:11
0

What about :

#define DEFAULT_VALUE 0
#define DEFAULT(value, target) memset(&target, value, sizeof target)
#define DEFAULT_ZERO(target) DEFAULT(DEFAULT_VALUE, target)

struct A a;
DEFAULT_ZERO(a);

We are trying to be really generic here, however, I don't think the default value will ever change. The memset function doesn't know anything about the fields in your struct. memset only copies a byte (an unsigned char) to the first n bytes of the string pointed to ( &target). So, I don't think we'll make sense to set the all bytes of struct to different value from 0.

Giuseppe Pes
  • 7,772
  • 3
  • 52
  • 90
  • Yeah, use of a macro has the potential to work, in practice. Though the above is no good, because the "zero" nature of the default value is still defined in the source code by the use of the `DEFAULT_ZERO` macro. I could have a `DEFAULT_A` macro, for sure, but the end result won't be elegant. – Rattus Ex Machina Jun 02 '15 at 12:35
  • you can use `DEFAULT(value, target)` to use any value; – Giuseppe Pes Jun 02 '15 at 12:36
  • I thought you wanted to use zero as default value, so I don't understand the problem. What do you mean with " default value is still defined in the source code "?? – Giuseppe Pes Jun 02 '15 at 12:37
  • Today, the default value is zero. Tomorrow, who knows. That's the problem, I want the definition that the default value is zero to be in the header. I appreciate I can do this with a macro stored in the header, I'm just looking for the most correct way to do this, in C99. – Rattus Ex Machina Jun 02 '15 at 12:39
  • you can just do ` #define DEFAULT_VALUE 0`. Note that all this macro code can live in the header file. It doesn't need to be in the source file. – Giuseppe Pes Jun 02 '15 at 12:43
  • I see what you're saying, but assigning a DEFAULT_VALUE of 1, for instance, isn't very meaningful. Only a DEFAULT_VALUE of 0 is likely to be meaningful for this byte-by-byte definition. I want to define the struct's value in the usual way, but just shortcut all the explicit initialization of fields (of which there are many) to zero. – Rattus Ex Machina Jun 02 '15 at 12:46
  • Yes, right! Now we are in the same page. I updated my answer saying exactly that. The function you want, then depends on the type of struct you are initialising. – Giuseppe Pes Jun 02 '15 at 12:52
0

Use a {0} for each structure level.

struct X {
  struct {
    int p;
    int q;
  } a;

  struct {
    int m;
    int n;
  } b;

  int c, d, e;
};

// struct X x = { 0 };
struct X x = { { 0 }, { 0 }, 0 };

I now see this relates to "GCC versions < 5". This approach tested with "4.9.2"

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Yes, this works, but the actual nested structure in question is many levels deep, and its declaration is in constant flux. Keeping these zero initializers up to date, in light of that, would be a real pain. – Rattus Ex Machina Jun 02 '15 at 14:10
  • @Rattus Ex Machina Code could put a `struct X g_0;` in global memory (which is zeroed at program start) and then use `struct X x = g_0;` where ever a zeroed `struct X` is needed. – chux - Reinstate Monica Jun 02 '15 at 14:15
  • Yes, that works too, sure. However, I'm trying to get to something different in the question, which I've just updated as you've seen. This shouldn't be the answer to the new question, if it is I need to work on the question more... let me see. – Rattus Ex Machina Jun 02 '15 at 14:20