Immutable structs in C

c tricks

Immutability is a popular term to qualify an object as totally constant right after its creation. Simple immutable variables aren't so interesting (excluding may be the immutable strings) but structs and objects are. In some languages all the fields of an object are immutable by default (f.ex. Rust), in other we can declare them as immutable. C#:

class ImmutableType {
  public readonly double x; 
  public Immutable(double _x) { x = _x; }
}

Note the constructor Immutable — it is the single place where we can change immutable fields in C#.

But in C we also can do something like this using good old const modifier:

typedef struct {
  int f;
  const int cf;
} s_t;

int main() {
  s_t s = {1,2};
  s.f = 3;
  s.cf = 4; 
  return 0;
}

This, as expected, generates an error on s.cf = 4 assignment.

You can even write something like this

int x = 1, y = rand();
s_t s = {x,y};

actually initializing a constant with random value. But not

s = {x,y};

and even not

int x = rand();
int y = rand();
s_t t = {x,y};
s = t;

because it isn’t an initialization! (You of course still can do assignments of pointers to structs.)

But f.ex. in construction function you can use the cast

*(int*)&s.cf = 1;

C is still C, so there is no such a thing you are completely forbidden to do.

 

Footnote

They say, that immutability makes code faster for many reasons.

The simple reason for this is that the compiler can exploit the knowledge of constant nature of immutable objects. The more complex reason is that the immutability allows us to use non-standard (for C) data structures and algorithms (f.ex. persistent data types) and thus to decrease side effects of our code.

As GC, that in some sense simulates a computer with infinite memory, total immutability simulates a computer with readonly memory, allowing a program to make many assumptions about data.

It's all interesting, but let's compile this:

int n = 0;
s_t s = {1,2};
for (int i = 0; i < 256; i++) {
  n += s.f;
  n += s.cf;
}
return n;

$ cc --std=c99 -O2 -S -masm=intel const.c -o const.asm

The result will be just

main:
  mov   eax, 768
  ret

Ok] Let's do something not so predicable:

int n = 0;
s_t s = {rand(),rand()};
for (int i = 0; i < rand(); i++) {
  n += s.f;
  n += s.cf;
}
return n;

Listing:

  call  rand
  mov   r12d, eax
  call  rand
  add   r12d, eax
  jmp   .L2
.L3:
  add   ebp, r12d  ; accumulation
  add   ebx, 1
.L2:
  call  rand
  cmp   ebx, eax
  jl    .L3
  mov   eax, ebp

Note that compiler treats normal f and constant field cf both invariant to the loop. So C compilers are smart enough (there is even special volatile keyword to make them stupid enough]).

As we see at least in simple cases there're no advantages besides readability and better correctness and possibilities for other high-level things for immutable structs in C. Though may be in more complex cases it can help the compiler?

Let’s make our structure be modifiable on nonpredicable moments by another thread in the program:

pthread_t tid;
s_t s = {1,2};

void* thread(void *arg) {
  while(1) {
    s.f = rand();
    *(int*)&s.cf = rand(); // dirty hack
  }
  return NULL;
}

int main() {
  pthread_create(&tid, NULL, &thread, NULL);
  int n = 0;
  for (int i = 0; i < rand(); i++) {
    n += s.f;
    n += s.cf;
  }
  return n;
}

The main loop compiles to

.L8:
  add   ebx, DWORD PTR s[rip]
  add   ebp, 1
  add   ebx, DWORD PTR s[rip+4] ; reloading of "constant"!
.L7:
  call  rand
  cmp   ebp, eax
  jl    .L8

This means that the compiler is ready to all the dirty hacks we can do in C and not trusts us and our const modifiers]

 

See also:

 

shitpoet@gmail.com

 



 

free hit counters