2025-12-27

array literals in c

part of c programming

this document describes how initial array contents can be expressed in c. it complements "array representations" by focusing on construction syntax and initialization forms, not storage models or mutation semantics.

c does not have array values in the abstract sense. it has objects with storage duration that may be initialized using literal syntax. all mechanisms described below operate within that constraint.

purpose and limits

  • array literals in c exist only as part of object initialization
  • there is no general expression that evaluates to an array value
  • literal syntax therefore always implies

    • a concrete object

    • a defined storage duration

    • a fixed element type

aggregate initialization

  • initializer lists used at object definition
  • applies to arrays and structs
  • object has automatic or static storage duration
int a[] = {1, 2, 3};

int b[3][2] = {
  {1, 2},
  {3, 4},
  {5, 6}
};
  • initializer appears only at the point of definition
  • size may be inferred from the initializer
  • for multidimensional arrays, all but the first dimension must be known
  • large initializers may be placed in separate files via include

compound literals

  • unnamed objects introduced by a type name followed by an initializer
  • available since c99
  • usable as expressions
int *a = (int[]){1, 2, 3, 4};
  • storage duration

    • automatic inside a function
    • static at file scope
  • commonly used to

    • initialize pointers

    • pass literal arrays to functions

void print_array(int *a, size_t n);

print_array((int[]){1, 2, 3, 4}, 4);
  • semantically close to aggregate initialization
  • main distinction is the absence of a declared object name

literal data and the heap

  • heap-allocated arrays cannot be initialized directly

  • literal syntax can only produce temporary or static objects

  • common pattern

    • allocate

    • copy from a literal source

int *a = malloc(4 * sizeof(int));
memcpy(a, (int[]){1, 2, 3, 4}, 4 * sizeof(int));
  • compound literals are often used as the copy source
  • allocation failure handling follows normal malloc conventions

structs as array carriers

  • arrays embedded in structs participate in aggregate initialization
  • the struct becomes a value-like container for the array
typedef struct {
  int data[3];
} int_array3_t;

int_array3_t x = { .data = {1, 2, 3} };
  • the entire struct can be copied, passed, or returned
  • element access requires a field name
x.data[1];
  • layout may include padding for alignment
  • copying semantics are defined at the struct level

flexible array members

  • since c99, the last struct member may be an array of unspecified size
typedef struct {
  size_t size;
  int data[];
} int_array_t;
  • such structs cannot be initialized with array literals
  • they must be allocated dynamically
  • access is always via pointer
  • they do not have value-like copy semantics

macros and literal forwarding

  • macros may expand to initializer lists or compound literals
  • used to reduce verbosity or simulate literal syntax
#define int_array(...) ((int[]){__VA_ARGS__})

int *a = int_array(1, 2, 3, 4);
  • advantages

    • concise call-site syntax
    • no additional runtime code
  • limitations

    • type is fixed or implicit

    • debugging and error reporting are weaker

    • semantics depend on expansion context

heap-targeting macros

  • macros may combine allocation and copying
  • relies on nonstandard extensions or statement expressions
#define new_array(size, ...) ({       \
  int *_a = malloc(size * sizeof(int)); \
  if (_a) {                            \
    int _t[] = {__VA_ARGS__};          \
    memcpy(_a, _t, size * sizeof(int)); \
  }                                   \
  _a;                                 \
})
  • not portable
  • semantics dominated by allocation policy, not literal syntax

functions as constructors

  • functions that allocate and populate arrays
  • fixed-arity or variadic
int *int_array4(int a, int b, int c, int d);
int *new_array(size_t n, ...);
  • these do not introduce new literal forms
  • they encode initialization procedurally
  • behavior is governed by calling convention and allocation semantics

summary

  • aggregate initialization and compound literals are the only native literal forms
  • all other techniques reuse these forms indirectly
  • literal syntax always implies an object and a storage duration
  • heap arrays require a two-step process: allocation plus initialization
  • representation and mutation concerns are treated separately in "array representations"