Initializing a struct pointer with char array

Viewed 109

I've encountered a similiar problem as described in another thread (perf_event_open - how to monitoring multiple events). I was able to solve it and the code is working, but I want to understand why this part actually works and how this is not a violation of any kind:

char buf[4096];
struct read_format* rf = (struct read_format*) buf;

struct read_format is defined as followed:

struct read_format {
uint64_t nr;
struct {
    uint64_t value;
    uint64_t id;
} values[/*2*/]; };

How does the compiler know to which value uint64_t nr should be initialized? Or how to initialize the inner struct right?

2 Answers

It doesn't The buffer is zero-initialized and the struct pointer is initialized with a pointer to the buffer.

It looks completely whack; however it really isn't. The read function is going to read as many structures into the buffer as fit.

The outer structure is variable-length. The advance loop looks like this:

    struct read_format *current = rf;
    if (readstructs(..., &current, 4096)) {
        for (;current;current=current->nr?((struct read_format *)((char *)current + current->nr)):NULL) {
        }
    }

These things appear in system-level OS calls to decrease the complexity of copying memory across security boundaries. The read side is easy and well-taught. The writer performs the operations necessary in filling the buffer to ensure this simple reader does not violate any system-level constraints. The code will work despite looking like it violates types left and right because the writer has set it up to work. In particular, the pointer will be aligned.

I've seen a similar method used in old file formats. Unfortunately that only follows the rules of the platform that wrote it (usually something ancient and far more permissive than a modern system) and leads to having to write a byte-at-a-time reader because the host doing the reading doesn't correspond.

This code is incorrect in Standard C:

char buf[4096];
read(fd1, buf, 4096);   // Assume error handling, omitted for brevity
struct read_format* rf = (struct read_format*) buf;
printf("%llu\n", rf->nr);

There are two issues -- and these are distinct issues which should not be conflated -- :

  • buf might not be correctly aligned for struct read_format. If it isn't, the behaviour is undefined.
  • Accessing rf->nr violates the strict aliasing rule and the behaviour is undefined. An object with declared type char cannot be read of written by an expression of type . unsigned long long. Note that the converse is not true.

Why does it appear to work? Well, "undefined" does not mean "must explode". It means the C Standard no longer specifies the program's behaviour. This sort of code is somewhat common in real code bases. The major compiler vendors -- for now -- include logic so that this code will behave as "expected", otherwise too many people would complain.

The "expected" behaviour is that accessing *rf should behave as if there exists a struct read_format object at the address, and the bytes of that object are the same as the bytes of buf . Similar to if the two were in a union.

The code could be made compliant with a union:

union
{
    char buf[4096];
    struct read_format rf;
} u;

read(fd1, u.buf, sizeof u.buf);
printf("%llu\n", u.rf->nr);

The strict aliasing rule is "disabled" for union members accessed by name; and this also addresses the alignment problem since the union will be aligned for all members.

It's up to you whether to be compliant, or trust that compilers will continue put practicality ahead of maximal optimization within the constraints permitted by the Standard.

Related