Compound Literals in C
Tags: c
Published: 2024-02-09 02:51:58

---
title: Compound Literals in C
published: true
description: A description of compound literals in C.
tags: #c
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-02-08 15:47 +0000
---

## Introduction

Among other things, [C99](https://en.wikipedia.org/wiki/C99) added [compound literals](https://en.cppreference.com/w/c/language/compound_literal) for arrays, `struct`s, and [`union`](https://dev.to/pauljlucas/unions-in-c-1ojj)s. Just as `42` specifies an `int` literal, a compound literal specifies a literal for an array, `struct`, or `union`. Consider:

```c
struct point {
  int x, y;
};
typedef struct point point_t;

void draw_circle( point_t center, unsigned radius );
```

Rather than having to create a temporary object like:

```c
point_t p = { 1, 2 };
draw_circle( p, 5 );
```

a `point_t` literal can be specified “inline”:

```c
draw_circle( (point_t){ 1, 2 }, 5 );
```

That is, you put the type of the literal you want to specify between `()` and follow that by `{}` enclosing the type’s value(s).  The syntax is similar to a cast, but the result is an _lvalue_ — more later.

The syntax is the same for arrays:

```c
void print_list( char const *list[] );

void f() {
  // ...
  print_list( (char const*[]){ "hello", "world", NULL } );
```

## Specifying Values

Values between `{}` can be specified the same as always, that is either in declaration order or via _designated initializers_.  For `struct`s or `union`s, a designated initializer is a `.` followed by the name of a member:

```c
p = (point_t){ .x = 1, .y = 2 };
```

For arrays, a designated initializer is an index between `[]`. For example:

```c
int *const a = (int[]){ [3] = 42 };
```

would create an array `[0,0,0,42]`.

When using designated initializers:

+ For `struct`s or `union`s, members may be specified in _any_ order; values for members _not_ specified are initialized to 0.
+ For arrays, indices may be specified in _any_ order; values for indices _not_ specified are initialized to 0.

To specify 0 for _all_ values:

+ Use `{ 0 }`, e.g., `(point_t){ 0 }`.
+ Starting in [C23](https://en.wikipedia.org/wiki/C23_(C_standard_revision)), you can omit the `0`, e.g., `(point_t){}`.

## Compound Literals are Lvalues

As mentioned earlier, even though the syntax of compound literals is similar to casts, compound literals are different in a fundamental way: unlike casts, compound literals are [_lvalues_](https://en.cppreference.com/w/c/language/value_category). Briefly:

+ An _lvalue_ is a value, can (but _not_ must) appear on the left-hand-side (hence, L-value) of `=` (the assignment operator), and can have its address taken via `&` (the address-of operator).

For compound literals, the important point is that you can take their address via `&`.  For example, if previous `draw_circle()` function were instead declared as:

```c
void draw_circle( point_t const *center, unsigned radius );
```

then you could do:

```c
draw_circle( &(point_t){ 1, 2 }, 5 );
```

that is put `&` in front. This is quite convenient for large `struct`s: simply pass the pointer instead of copying the entire `struct`.

## Compound Literal Lifetime

For an object that has a name, the lifetime of the object is either forever (if declared at global scope) or is limited to the scope in which it’s declared. It’s the same for compound literals. For example, if you were to do:

```c
point_t *p;
do {
  p = &(point_t){ 1, 2 };
} while (0);              // Lifetime of literal ends here.
printf( "%d\n", p->x );   // Undefined behavior.
```

Accessing the pointed-to compound literal declared inside the scope via `p` outside the scope would result in [undefined behavior](https://dev.to/pauljlucas/undefined-behavior-in-c-and-c-3a20). The lesson is: the lifetime of a compound literal _must_ be at least as long as any pointer to it.

Starting in C23, you can optionally insert a storage class like `static`:

```c
  p = &(static point_t){ 1, 2 };
```

that will make it last forever (just like a `static` local variable inside a function).

For completeness, `const`, [`constexpr`](https://dev.to/pauljlucas/c-const-conundrum-j2l), `register`, and `thread_local` may also be used.

## Named Function Arguments

Suppose you have a function that puts some text on the screen in a particular typeface, size, and color at a particular location:

```c
void put_text( char const *text, typeface_t typeface,
               unsigned size, color_t color, point_t loc );

void f() {
  // ...
  put_text( "hello, world!", TYPE_HELVETICA, 12,
            COLOR_BLACK, (point_t){ 10, 50 } );
```

While calls to such a function are fairly clear, you might occasionally have to look at the function declaration to remember the order in which to specify the arguments.  For such functions, it would be nice if you could specify the arguments by name (hence the order wouldn’t matter).  But how?

To do this, we can use a `struct` containing the arguments and change the declaration of `put_text()` to use it:

```c
struct put_text_args {
  char const *text;
  typeface_t  typeface;
  unsigned    size;
  color_t     color;
  point_t     loc;
};
typedef struct put_text_args put_text_args_t;

void put_text( put_text_args_t const *args );

void f() {
  // ...
  put_text( &(put_text_args_t){
    .text = "hello, world!",
    .typeface = TYPE_HELVETICA,
    .size = 12,
    .color = COLOR_BLACK,
    .loc = (point_t){ 10, 50 }
  } );
```

> Assume that `typeface_t` and `color_t` are [enumerations](https://dev.to/pauljlucas/enumerations-in-c-ae7). For this example, their definition doesn’t matter.

While that’s better, it’s kind of ugly to have to specify `&(put_text_args_t){}`.  To hide the ugliness, we can use a [macro](https://dev.to/pauljlucas/cc-preprocessor-macros-fh5):

```c
#define put_text(...) \
  put_text( &(put_text_args_t){ __VA_ARGS__ } )

void f() {
  // ...
  put_text(
    .text = "hello, world!",
    .typeface = TYPE_HELVETICA,
    .size = 12,
    .color = COLOR_BLACK,
    .loc = (point_t){ 10, 50 }
  );
```

> The name of the macro can match the name of the function and _not_ cause an infinite preprocessor macro expansion loop because the preprocessor will _not_ expand a macro that references itself.

## Default Arguments

Now suppose you want to make some of those arguments have default values. It turns out you can specify the same designated initializer more than once and the value of the last one “wins”:

```c
#define put_text_defaults     \
  .typeface = TYPE_HELVETICA, \
  .size = 12,                 \
  .color = COLOR_BLACK

#define put_text(...) \
  put_text( &(put_text_args_t){ put_text_defaults, __VA_ARGS__ } )

void f() {
  // ...
  put_text(
    .text = "hello, world!",
    .size = 24,
    .loc = (point_t){ 10, 50 }
  );
```

The `put_text_defaults` macro defines the desired default values that are used in `put_text()`.  If `__VA_ARGS__` contains a matching designated initializer such as `.size`, it will override the value specified by `put_text_defaults`.

While this works, your compiler will likely give a “initializer overrides prior initialization of this subobject” warning since it usually means you made a mistake. To fix that, we can temporarily disable the warning by using [`_Pragma`](https://en.cppreference.com/w/c/preprocessor/impl):

```c
#ifdef __GNUC__
#define OVERRIDE_ARGS(FN,...)                                     \
  _Pragma( "GCC diagnostic push" )                                \
  _Pragma( "GCC diagnostic ignored \"-Winitializer-overrides\"" ) \
  FN( &(FN##_args_t){ FN##_defaults, __VA_ARGS__ } )              \
  _Pragma( "GCC diagnostic pop" )
#else
// ... OVERRIDE_ARGS() for other compilers ...
#endif

#define put_text(...)  OVERRIDE_ARGS( put_text, __VA_ARGS__ )
```

Since warnings are compiler-specific, you have to `#define` `OVERRIDE_ARGS()` for each compiler you plan to use.

## Not in C++

Compound literals are _not_ part of any C++ standard; however, many compilers support them as an extension. Note also that their lifetime is _shorter_ in C++: it extends _only_ until the end of the _expression_ (not scope) in which they’re used.  As such, code that would be perfectly OK in C may result in undefined behavior in C++.

Of course, C++ has constructors that can often replace the need for compound literals. My advice is not to use compound literals in C++.

## Conclusion

Compound literals are quite handy for creating objects “inline” eliminating what otherwise would have been temporary variables. As shown, they can also be used to enable passing arguments to functions by name.