can I reduce a long array initialization to a shor

2019-08-13 00:23发布

I have some C99 code that looks like this:

struct foo {
    char* buf;
    size_t buf_sz;
};

struct foo list[3];
list[0].buf = "The first entry";
list[0].buf_sz = strlen(list[0].buf);
list[1].buf = "The second entry";
list[1].buf_sz = strlen(list[1].buf);
list[2].buf = "The third entry";
list[2].buf_sz = strlen(list[2].buf);

Is there a shorthand way of writing this in an initializer list? Is something like this safe?

struct foo list[] = {
    { "The first entry", strlen(list[0].buf) },
    { "The second entry", strlen(list[1].buf) },
    { "The third entry", strlen(list[2].buf) }
};

标签: c
4条回答
虎瘦雄心在
2楼-- · 2019-08-13 00:54

How about this one:

#define S(str)  { str, sizeof(str) - 1 }
struct foo list[] = {
    S("The first entry"),
    ...
};
#undef S

I #undefed the macro right after the initialiser. Such short names should be used in a compact&short piece of code only. If you want to use it elsewhere, use a self-explanatory name (it should include the name of the struct type) and remove the #undef.

Note this only works with a string literal. But it works at file-level and does not introduce any run-time overhead. Thus it also works for const struct list[], i.e. a constant array.

Be cautious about the sizeof(str) - 1. This is the same as strlen yields, but the allocated memory is actually sizeof(str).

查看更多
爷、活的狠高调
3楼-- · 2019-08-13 01:00

Try

#define STR1 "The first entry"

struct foo list[] = {
  {STR1, sizeof STR1},
  ...

Please note that the size is set to strlen(STR1) + 1, due to the "string"'s 0-terminator. To adjust this do

struct foo list[] = {
  {STR1, sizeof STR1 - 1},
  ...
查看更多
再贱就再见
4楼-- · 2019-08-13 01:05

You cannot use your second definition for a static duration foo array, because initializer lists for static duration objects are required to be constant. Draft n1256 for C99 states in 6.7.8 Initialization §4

4 All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.

Even for automatic (block scope) foo arrays it is unspecified by the standard whether your second syntax is valid or undefined behaviour. Item §23 of same paragraph says

23 The order in which any side effects occur among the initialization list expressions is unspecified.133)

and note 133 precises:

In particular, the evaluation order need not be the same as the order of subobject initialization

That means that if would be acceptable for a compiler implementation to initialize the buf_sz members before the buf members which would lead to UB (even if it gives correct result without any warning on my old MSVC2008).

For that reasons my advice is to initialize only the buf members, and then use a loop to set the buf_sz members that require the structs to be already initialized - as a side effect, if can be used the same for static or dynamic storage duration:

struct foo list[] = {
    { "The first entry" },
    { "The second entry" },
    { "The third entry" }
};

and later in code:

int i;
for(i=0; i<sizeof(foo)/sizeof(foo[0]); i++) {
    foo[i].buf_sz = strlen(foo[i].buf);
}
查看更多
倾城 Initia
5楼-- · 2019-08-13 01:15

One alternative would be:

struct foo {
    char* buf;
    size_t buf_sz;
};

...

struct foo list[] = {
    { "The first entry" },
    { "The second entry" },
    { "The third entry" }
};

...

for ( int i = 0; i < sizeof(foo)/sizeof(foo[0]); i++ )
    list[i].buf_sz = strlen(list[i].buf); // number of chars in string

If you need to put in the number of actual bytes consumed by the string, then you'd need a +1 on your strlen. I don't know what the API requires.

查看更多
登录 后发表回答