user366312
user366312

Reputation: 16988

What is going on in this code?

Can somebody please explain the code for me?

Why 32 bit UINT?

Why 0xff?

What are 4 right shifts doing here?

int writeUINT32little(FILE *f, UINT32 i)
{    
    int rc;    
    rc = fputc((i & 0xff), f);    
    if (rc == EOF)       return rc;    
    rc = fputc(((i >> 8) & 0xff), f);    
    if (rc == EOF)       return rc;    
    rc = fputc(((i >> 16) & 0xff), f);    
    if (rc == EOF)       return rc;    
    return fputc(((i >> 24) & 0xff), f);
}

Upvotes: 2

Views: 1560

Answers (2)

Amit
Amit

Reputation: 46341

What this function supposedly does is write a 32bit unsigned integer to a file stream in little-endian.

What it really does is, instead of doing one small simple task and getting it right, it tries to do two things together and gets both wrong.

Before I go into why this is wrong, I'll quickly answer the question itself:

  • Why UINT32? Don't know. It's non standard and better be replaced with uint32_t.
  • Why 0xff? Used as a mask to keep the lower 8 bits in a larger variable (32 bits)
  • What are 4 right shifts doing here? The shifts are used to bring every byte in the original UINT to the rightmost bits in the correct "little-endian" order.


Now, with that out of the way...

Why this is wrong?
A char is guaranteed to be at least 8 bits wide, but it could be larger. This means that fputc will probably write 8 bits per call, but it might write more. What this means is that on a certain architecture where a char is (for example) 16 bits, the function will write 64 bits per call and it won't be little-endian at all. Additionally, the function is very careful checking for errors but doesn't expose the relevant failure information to the caller (how many bytes were written?).

What's a better design?
First, as always, do one small thing and do it right:

uint32_t toLittleEndian(uint32_t v)
{
    uint32_t result = v >> 24;
    result |= v << 24;
    result |= (v >> 8) & 0xf0;
    result |= (v << 8) & 0xf00;
    return result;
}

Note: this implementation is deliberately verbose. There's an optimisation opportunity using bit rotation operations but C has no native support for that and requires assembly code

This function only converts a big-endian 32 bit value to its equivalent little-endian representation (actually it alternates between the two and could be named switchEndian).

Next, we need to write the value to a file:

int write(uint32_t v, FILE *f)
{
    return fwrite(&v, sizeof(v), 1, f);
}

This will write exactly 32 bits wide integer, and return the number of bytes actually written. Additionally, it's reusable.

Next, we could create a convenience composite function if we want:

int writeLittleEndian(uint32_t v, FILE *f)
{
    return write(toLittleEndian(v), f);
}

Upvotes: 1

Chris
Chris

Reputation: 1623

This takes a 32-bit unsigned integer and outputs it to a file, byte-by-byte with the least significant byte first (i.e. little-endian).

0xff is the hexadecimal way of writing 255 in decimal, or 11111111 in binary. And & is the bitwise and function. Put together, (i & 0xff) masks out the least significant byte of the integer.

Then the shifts shift the whole thing over 8 bits and the process repeats to print the next byte.

Upvotes: 9

Related Questions