Home > other >  Compound literals in C: do they create duplicate copies?
Compound literals in C: do they create duplicate copies?

Time:01-14

I have always wondered about compound literals in C, do they create duplicate copies?

Take the following two examples for instance. The only difference between the two are few lines of code, respectively

book->book_id = book_id;
book->price = price;
book->isbn = isbn;

in the first example, and

*book = (Book) {
    .book_id = book_id,
    .price = price,
    .isbn = isbn
};

in the second example.

My question is simple: Does the second example allocate a Book structure in the stack first and then copy it into the heap, or does the second example behave exactly like the first example, and simply populates the structure already allocated in the heap without touching the stack?

Example 1

#include <stdio.h>
#include <stdlib.h>

typedef struct Book_T {
    unsigned int book_id;
    unsigned int price;
    long unsigned int isbn;
} Book;


Book * new_book (
    const unsigned int book_id,
    const unsigned int price,
    const long unsigned int isbn
) {

    Book * book = malloc(sizeof(Book));

    if (!book) {

        return NULL;

    }

    book->book_id = book_id;
    book->price = price;
    book->isbn = isbn;

    return book;

}


int main (
    const int argc,
    const char * argv[]
) {

    Book * my_book = new_book(12944, 39, 9783161484100);

    if (!my_book) {

        fprintf(stderr, "Error allocating memory\n");
        return 1;

    }

    /*  Do something with `my_book`...  */

    free(my_book);

    return 0;

}

Example 2

#include <stdio.h>
#include <stdlib.h>

typedef struct Book_T {
    unsigned int book_id;
    unsigned int price;
    long unsigned int isbn;
} Book;


Book * new_book (
    const unsigned int book_id,
    const unsigned int price,
    const long unsigned int isbn
) {

    Book * book = malloc(sizeof(Book));

    if (!book) {

        return NULL;

    }

    *book = (Book) {
        .book_id = book_id,
        .price = price,
        .isbn = isbn
    };

    return book;

}


int main (
    const int argc,
    const char * argv[]
) {

    Book * my_book = new_book(12944, 39, 9783161484100);

    if (!my_book) {

        fprintf(stderr, "Error allocating memory\n");
        return 1;

    }

    /*  Do something with `my_book`...  */

    free(my_book);

    return 0;

}

CodePudding user response:

In the abstract machine (which is how C is defined) the compound literal at block scope creates an object in automatic storage. The object will be destroyed at the end of the function.

"stack" and "heap" are implementation details (which some implementations don't have). The only requirements on the implementation are to produce the same observable behaviour as the abstract machine, it does not have to replicate the abstract machine's memory usage. So the real machine could generate the same executable for both of your examples.

CodePudding user response:

If a compiler can see everything that will be done with a constant literal object, it may be able to omit code that would reserve and release space for it. For example, given:

struct S foo;
...
foo = (struct S foo){1,2,3};

a compiler could generate code that would simply store 1, 2, and 3 directly into the first items of foo and zero to the rest (if any), rather than generating a new temporary object and copying it.

Unfortunately, there is no way to invite a compiler to refrain from constructing a temporary object in cases where code takes the address of a compound literal and uses it in ways the compiler can't see. Given e.g.

extern void someFunction(struct foo const *p);
...
someOutSideFunction(&(struct foo){1,2,3});

a compiler would not be allowed to substitute:

extern void someFunction(struct foo const *p);
...
static struct foo const __const24601 = {1,2,3};
someOutSideFunction(&__const24601);

unless it knew that someFunction would not both cause the aforementioned function call to get invoked recursively and compare the addresses of the pointers passed to the nested invocations. As of C18, the only way to allow the compiler to generate code that is as efficient as the latter form would be for a programmer to define a named constant object and pass its address as shown in that example.

  •  Tags:  
  • Related