Home > OS >  Pointer and array usage confusion
Pointer and array usage confusion

Time:07-09

There is a code excerpt from official Quake 2 source code:

unsigned        *buf;
dheader_t       header;
...
header = *(dheader_t *)buf; // #1
    for (i=0 ; i<sizeof(dheader_t)/4 ; i  )
        ((int *)&header)[i] = LittleLong ( ((int *)&header)[i]); // #2

Can someone please explain me in the most possible details what do the line #1 and then #2 really do because I'm little or more confused...

P.S

Here is the rest of the definitions if it helps:

int     LittleLong (int l) {return _LittleLong(l);}
...
typedef struct
{
    int         ident;
    int         version;    
    lump_t      lumps[HEADER_LUMPS];
} dheader_t;

P.S. 2

I've linked above the original full source file code if needed.

CodePudding user response:

This is some seriously brittle code and you shouldn't write code like this.

What it does is to go through the struct int by int, then does something with each such int inside _LittleLong. Very likely this function performs a 32 bit conversion from a big endian integer to a little endian one. Meaning that the source you are looking at is likely something related to reception of IP packages.

Checking at what the code does step by step:

  • for (i=0 ; i<sizeof(dheader_t)/4 ; i ) is a sloppier way of writing sizeof(dheader_t)/sizeof(int). That is: iterate through the struct int by int, chunks of 32 bits.
  • (int *)&header converts from a dheader_t* to a int*. This is actually well-defined by a special rule in C that allows us to convert from a pointer to a struct to a pointer to its first member or vice versa and the first member is int.
  • However, doing so is only well-defined for the first member. Instead they take the converted int* and apply array dereferencing on it: ((int *)&header)[i]. This is undefined behavior in C, a so-called strict aliasing violation, and could also cause alignment problems in some situations. Bad.
  • The int read from the struct through this dereferencing is then passed along to LittleLong which very likely does a big -> little endian conversion.
  • ((int *)&header)[i] = and here it is written back to where it was grabbed from.

Better, safer, well-defined and possibly faster code could look like:

void endianify (dheader_t* header)
{
  _Static_assert(sizeof(dheader_t)%sizeof(uint32_t)==0,
                 "Broken struct: dheader_t");

  unsigned char* start = (unsigned char*)header;
  unsigned char* end   = start   sizeof(dheader_t);
  
  for(unsigned char* i=start; i!=end; i =sizeof(uint32_t))
  {
    uint32_t tmp;
    memcpy(&tmp,i,sizeof(uint32_t));
    i[0]= (tmp >> 24) & 0xFF;
    i[1]= (tmp >> 16) & 0xFF;
    i[2]= (tmp >>  8) & 0xFF;
    i[3]= (tmp >>  0) & 0xFF;
  }
}

Disassembly:

endianify:
    mov     eax, DWORD PTR [rdi]
    bswap   eax
    mov     DWORD PTR [rdi], eax
    mov     eax, DWORD PTR [rdi 4]
    bswap   eax
    mov     DWORD PTR [rdi 4], eax
    mov     eax, DWORD PTR [rdi 8]
    bswap   eax
    mov     DWORD PTR [rdi 8], eax
    mov     eax, DWORD PTR [rdi 12]
    bswap   eax
    mov     DWORD PTR [rdi 12], eax
    mov     eax, DWORD PTR [rdi 16]
    bswap   eax
    mov     DWORD PTR [rdi 16], eax
    ret
  •  Tags:  
  • c
  • Related