2024-11-17

array literals in c

part of c programming

sometimes it would be useful to assign array content literally by listing the contents. something like this is possible in most scripting languages:

a = [1, 2, 3, 4]

but c is limited and the following shows some possible options and alternatives.

option: stack-allocated aggregate arrays

int a[] = {1, 2, 3};
int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
  • can only be used once where a variable is defined
  • can only be used for stack arrays, not for heap arrays (for example, allocated by malloc or similar functions)
  • can be passed to and accessed in sub-functions
  • can not be accessed after the declaring function has returned unless declared as a static variable
  • type names like int[4][2] restrict variable and function parameter types, requiring at least the last dimension, for example int[][2]. pointer types allow flexibility but need type casting
  • #include can be used to keep large definitions in separate files

more information:

option: compound literals

int *a = (int[]){1, 2, 3, 4};
void print_array(int *arr, size_t size);

print_array((int[]){1, 2, 3, 4}, 4);
  • available since c99
  • only valid within a function, just like stack-allocated aggregate arrays
  • can be used to initialize pointers or pass arrays to functions

with heap allocation

int *a = malloc(4 * sizeof(int));
if (!a) exit(1);
memcpy(a, (int[]){1, 2, 3, 4}, 4 * sizeof(int));

with inline functions

static inline int *new_array(size_t size, int values[]) {
  int *a = malloc(size * sizeof(int));
  if (a) memcpy(a, values, size * sizeof(int));
  return a;
}

int *a = new_array(4, (int[]){1, 2, 3, 4});

option: structs with arrays

typedef struct int_array3 {
  int data[3];
} int_array3_t;
int_array3_t x = {.data = {1, 2, 3}};
x.data[1]
int_array3_t y = x;

this is aggregate initialization with the added benefit that the whole array can be used like a scalar value.

  • a field name must be declared and used to access elements
  • stack-allocated structs can be passed to functions, copied, and returned including elements like other simple values - even as a return value
  • struct field types align to multiples of their size, with padding potentially added between members and at the end to maintain alignment

flexible array members

since c99, the last member of a struct can be an array of unspecified size.

typedef struct {
  size_t size;
  int data[];
} int_array_t;

however, to make use of this, the struct has to be allocated on the heap. since it is only accessible via a pointer, the resulting struct can not be copied like a scalar value.

option: array creation expressions

functions that return heap arrays.

#include <stdlib.h>

int* int_array4(int a, int b, int c, int d) {
  int* x = malloc(4 * sizeof(int));
  if (!x) return 0;
  x[0] = a;
  x[1] = b;
  x[2] = c;
  x[3] = d;
  return x;
}
int* e = int_array4(2, 3, 4, 5);

variadic functions

this can also be used with a variable number of arguments.

#include <stdarg.h>

int *new_array(size_t n, ...) {
  int *a = malloc(n * sizeof(int));
  if (!a) return 0;
  va_list args;
  va_start(args, n);
  for (size_t i = 0; i < n; i += 1) {
    a[i] = va_arg(args, int);
  }
  va_end(args);
  return a;
}

int *a = new_array(4, 1, 2, 3, 4);

error handling

memory allocation can fail, therefore the question of how to handle this failure arises.

option: return a null pointer

this is similar to malloc, which also just returns a null pointer on failure. this has to be checked to avoid null pointer access

option: exit the program

no further error-checking code is needed, nor relevant, with this option. however, this option doesnt compose well, as it exits the calling process - a behavior that is particularly undesirable when the code is used within a library.

example, inside the array creating function:

if (!x) exit(1);

option: macro that sets values

  • the data area must have been declared and allocated separately
  • is usually not an expression that can return an array pointer like a function
  • the same macro can be used for any type
#define array_set4(x, a, b, c, d) x[0] = a; x[1] = b; x[2] = c; x[3] = d;
#include <stdlib.h>

int* x = malloc(4 * sizeof(int));
if (!x) exit(1);
array_set4(x, 2, 3, 4, 5);

with a variable number of arguments

this allows setting any number of elements.

#define new_array(...) ((int[]){__VA_ARGS__})

int *a = new_array(1, 2, 3, 4);

with heap allocation

#define new_array(size, ...) ({ \
  int* _arr = malloc(size * sizeof(int)); \
  if (_arr) { \
    int _tmp[] = {__VA_ARGS__}; \
    memcpy(_arr, _tmp, size * sizeof(int));\
  } \
  _arr; \
})

int *a = new_array(4, 1, 2, 3, 4);