C - dark arts

innocentzero

2026-03-14

C - dark arts

C: arrays are not pointers

  // in file 1
  int arr[10];

  // in file 2
  extern int *arr;

This is semantically incorrect and leads to an access violation. It thinks that arr holds the contents to the pointer. Making a reference to arr[i] is equivalent to saying that start at wherever arr points, move over i places using the contents of arr[0..8] as the pointer value.

Instead just declare it with extern int arr[].

Pointers to string literals

This is READ-ONLY memory as it is hardcoded to the .text section of the binary! Trying to deref it at runtime is a page-fault in most cases since that section of the binary is marked read-only by the OS.

On the other hand, if they are initialized by an array, then you can see in the disassembly that they are actually loaded on the stack and can use it.

Shared libraries

They need to have the names libname.so where name is what you link with -lname. LD_LIBRARY_PATH and LD_RUN_PATH are used for static and dynamic linking respectively.

Const and volatile

That is to say,

void f(int * const p);

is valid. However, the above example is meaningless as the pointer is copied into the function. It makes sense for int const * p as that implies that the underlying data is constant.

Volatile is for raw hardware and basically means a value should be looked up from hardware.

Restrict keyword

Used for function optimization. If a pointer is an argument to a function, then the function is not aliased at all, atleast never written into. Mostly used as a hint to the compiler. However, it better not be used unless you're sure what you're doing.

Padding bytes in structs

You can optionally specify padding bytes in struct fields and also limit their size to a fixed number of bytes. Once again, useful in embedded.

Offsets

If you have a pointer to a struct that is =NULL=, but you use the -> operator to get a member and then take its address, it will give the size of the member. Basically acting as if the member is stored at memory 0.

Another thing to note would be the fact that the pointer to the first member of the struct is also the pointer to the struct.

Better yet, just use offsetof macro.

Unions

Just like structs, if the composing members of the union all have the same type, say int, then the pointer to the union is also a pointer to the int, no matter which one of the variants was set last.

Trigraph

Some ancient feature you shouldn't be bothered about. Basically ??! getting converted to pipe symbol and other shenanigans.

Passing Multidimensional VLAs to functions

#include <stdio.h>

void print_matrix(int h, int w, int m[h][w])
{
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++)
            printf("%2d ", m[row][col]);
        printf("\n");
    }
}

int main(void)
{
    int rows = 4;
    int cols = 7;

    int matrix[rows][cols];

    for (int row = 0; row < rows; row++)
        for (int col = 0; col < cols; col++)
            matrix[row][col] = row * col;

    print_matrix(rows, cols, matrix);
}