1

I want to write the 4 bytes of an int32_t to a binary file in big-endian order. I used fwrite() directly with a pointer to my int32_t, and it somewhat works, but the problem is that my integer is written in little-endian order, with the smallest bytes written first. For example, if I write:

int32_t specialInt = 262;
fwrite(&specialInt, 4, 1, myFile);

and I open it with my hex editor, I see:

06 01 00 00 ...

which is backwards compared to how I want it. I would like:

00 00 01 06 ...

How should I get my int_32t to be in big-endian order? Is there a built-in C library function that will get the bytes in the correct order, or should I use memcpy() to put the bytes into a temp char array, then write the bytes one by one backwards into the file?

Felix An
  • 309
  • 4
  • 13
  • 3
    Assuming `CHAR_BIT == 8` ... `unsigned char value[4]; value[0] = (unsigned)specialInt >> 24; value[1] = (unsigned)specialInt >> 16; value[2] = (unsigned)specialInt >> 8; value[3] = (unsigned)specialInt;` and `fwrite(value, 4, 1, myFile);` – pmg May 12 '22 at 09:14
  • 2
    There will be numerous duplicates of this question on the site already. Basically you need to shift the individual bytes in place. There's also the `htons` etc non-standard functions for the benefit of PC programmers who are scared of trivial bitwise operators :) – Lundin May 12 '22 at 09:16
  • @pmg: `unsigned` is not guaranteed to be wide enough for `int32_t`. – Eric Postpischil May 12 '22 at 11:03
  • And all of that doesn't help with the difference between 2s-complement and sign+magnitude encoding. – Goswin von Brederlow May 12 '22 at 12:38
  • @GoswinvonBrederlow: `int32_t` is two’s complement. `int` may be one’s complement or sign-and-magnitude, but `int32_t` may not. – Eric Postpischil May 12 '22 at 13:48
  • @EricPostpischil Where in the standard does it say `int32_t` is two's complement? It's just an alias to one of the core integer types, which may or may not be two's complement. – Goswin von Brederlow May 12 '22 at 13:55
  • 1
    @GoswinvonBrederlow: C 2018 7.20.1.1 1 says “The typedef name `intN_t` designates a signed integer type with width *N*, no padding bits, and a two’s complement representation…” The standard does not say it is an alias to one of the core integer types. – Eric Postpischil May 12 '22 at 13:58
  • @EricPostpischil wow, learn something new every day. Didn't know the intN_t must be two's complement. Must be fun for integer promotion if you have a hypothetical CPU with sign+magnitude ints and intN_t type. As for the aliased part: C 2018 7.20.1.1 3 says: *it shall define the corresponding typedef names* – Goswin von Brederlow May 12 '22 at 14:07
  • @GoswinvonBrederlow, note, a compliant C compiler need not implement `intN_t` on non-2's complement machines. – chux - Reinstate Monica May 12 '22 at 14:15
  • @GoswinvonBrederlow: Yes, `int32_t` is a `typedef` name, but it is not necessarily a `typedef` name for one of the core integer types. A C implementation could define types `int`, `__intTwosComplement`, and `__intSignAndMagnitude`, and others, and then it could make `int32_t` an alias for `__intTwosComplement`. – Eric Postpischil May 12 '22 at 14:17
  • Does this answer your question? [convert big endian to little endian in C \[without using provided func\]](https://stackoverflow.com/questions/2182002/convert-big-endian-to-little-endian-in-c-without-using-provided-func) – Karl Knechtel Oct 25 '22 at 04:24

2 Answers2

4

Thanks pmg for writing the answer in the comment:

Assuming CHAR_BIT == 8:

unsigned char value[4];
value[0] = (uint32_t)specialInt >> 24;
value[1] = (uint32_t)specialInt >> 16;
value[2] = (uint32_t)specialInt >> 8;
value[3] = (uint32_t)specialInt;
fwrite(value, 4, 1, myFile);
pmg
  • 106,608
  • 13
  • 126
  • 198
Felix An
  • 309
  • 4
  • 13
  • 3
    Just FYI: this works irrespective of the endianness of the computer. – pmg May 12 '22 at 09:48
  • 2
    As a bonus it also works without various 3rd party muppet libraries that takes ten times longer to find/get working than it takes to write down this simple, readable code. – Lundin May 12 '22 at 13:12
1

You could use htonl() function from POSIX 2001 standard available in arpa/inet.h header. See https://linux.die.net/man/3/ntohl

Big endian is Internet byte order, known as "network byte order".

You need to transform int32_t to uint32_t. This cast is defined by C standard. Next transform it to network endian via htonl() and then write it to a file:

int32_t specialInt = 262;
uint32_t encodedInt = htonl((uint32_t) specialInt);
_Static_assert(sizeof encodedInt == 4, "Oops");
fwrite(&encodedInt, 4, 1, myFile);

It could be abbreviated a bit with a compound literal.

fwrite(&(uint32_t) { htonl((uint32_t)specialInt) }, 4, 1, myFile);
tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • 2
    `htonl()` exists in Windows too — include _either_ `` _or_ `` (or if including both do it in that order) — then `#pragma comment(lib, "ws2_32")`. If you are compiling with GCC or Clang you’ll have to link with `-lws2_32`. – Dúthomhas May 12 '22 at 11:15
  • You don't need to check if `uint32_t` is 4 bytes, just check that `CHAR_BIT` is 8 bits. That is, if you insist on portability to wildly exotic DSPs or fictional computers - otherwise just drop the assert. – Lundin May 12 '22 at 13:17
  • 1
    @Lundin, yes, I know about it. Those machines a quite rare though still in use. A new C standard should make CHAR_BIT equal to 8 mandatory. Make "de facto" standard ... a standard. – tstanisl May 12 '22 at 14:25