Use of struct initialization syntax for on-heap st

2020-04-17 07:05发布

问题:

I have this simple structure I want to initialize on the heap and return as a pointer in a function.

struct entry {
    const char* const key; // We don't want the key modified in any way
    const void* data;      // But the pointer to data can change
    struct entry* next;
};

There is one problem, I cannot calloc it and initialize members one by one because key is a const pointer. I found somewhere this syntax that works:

struct entry* entry = calloc(1, sizeof(struct entry));
*entry = (struct entry) { .key = key, .data = data, .next = NULL };

But I don't know what is going on with it: does it create an "anonymous" struct that is then copied to the place where *entry lives? Is that safe to use or should I prefer creating a local struct that is then copied with memcpy to the right location?

回答1:

The assignment you presented is not correct and should not compile.

The correct way of initializing an allocated struct with const members is to allocate some memory, create a temporary struct entry object, and then use memcpy to copy the object to the allocated memory:

void* mem = malloc( sizeof( struct entry ) );
struct entry temp = { key , data , NULL };
memcpy( mem , &temp , sizeof( temp ) );
struct entry* e = mem;


回答2:

This line:

*entry = (struct entry) { .key = key, .data = data, .next = NULL };

uses the assignment operator. The conditions for assignment operator (C11 6.5.16/2) include:

Constraints

An assignment operator shall have a modifiable lvalue as its left operand.

The definition of modifiable lvalue can be found in 6.3.2.1/1:

A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.

So *entry is not a modifiable lvalue, because its type is a struct that has a member with const-qualified type. Therefore it is a constraint violation to have *entry appear on the left-hand-side of the assignment operator.

The clang compiler (all versions I tried) appears to not give any diagnostic message for this constraint violation; this is clearly a compiler bug. gcc does give a diagnostic.


Regarding the second part of the question:

should I prefer creating a local struct that is then copied with memcpy to the right location?

As also explained by 2501, you can do this in C when writing into space allocated by the malloc family. (If you had declared a named struct entry object then it is less clear whether memcpying over it is permitted).