2

I have a struct in C:

typedef struct {
    char member_a;
    char member_b;
    char member_c;
    char member_d;
} mystruct;

From what I understand, C structs store their members in memory contiguously. If I print out the struct's memory I can see that is the case, but it looks like the order of the members is reversed.

mystruct m;
m.member_a = 0xAA;
m.member_b = 0xBB;
m.member_c = 0xCC;
m.member_d = 0xDD;
printf("%X\n", m);

This outputs:

DDCCBBAA

Is this because the struct's member's values are stored in memory in reverse order? So the memory would look something like this, if m was stored at memory location 0x00 and each location was 1 byte in size:

memory location value
0x00 0xDD
0x01 0xCC
0x02 0xBB
0x03 0xAA

is this always the case with C? is this compiler specific? architecture specific? other?

Using gcc on Mac

Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.17)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
northsideknight
  • 1,519
  • 1
  • 15
  • 24
  • 6
    `%X` format specifier expects `unsigned int`, but you passed `mystruct`. This invokes *undefined behavior*, allowing anything to happen. – MikeCAT Jun 10 '21 at 20:32
  • 5
    Also the reason of this specific result looks like you are on *little-endian* machine. – MikeCAT Jun 10 '21 at 20:33
  • More specifically, they are stored in memory as AA BB CC DD, but when viewed as a single integer on a Little Endian Machine, this means 0xDDCCBBAA (eg, the least significant digits, the little end, are stored first). If you looped over it bytewise, you would see what you expect. – Max Jun 10 '21 at 20:36
  • Yes, the memory is contiguous. Yes, the exact layout within the contiguous memory space may vary between platforms and architectures. There should be no problems or conflicts as long as you don't make any invalid assumptions. – Cheatah Jun 10 '21 at 20:38
  • Does this answer your question? [Detecting endianness programmatically in a C++ program](https://stackoverflow.com/questions/1001307/detecting-endianness-programmatically-in-a-c-program) – the busybee Jun 10 '21 at 20:38
  • You get a vote for a well-formatted question that shows effort. Structs or stored in memory in the order the members are declared. The address of the struct is the address of the first member. Beyond that there is no guarantee where the 2nd through last elements are aligned due to the C-standard leaving to each implementation where padding can be inserted to preserve alignment. POSIX provides the `offsetof()` function in `stddef.h` that can report the address of each member. – David C. Rankin Jun 10 '21 at 20:39
  • `printf("%p\n%p\n%p\n%p\n", &m.member_a, &m.member_b, &m.member_c, &m.member_d);` would be a legal way to check the addresses – and it won't revert the bytes either. – Aconcagua Jun 10 '21 at 20:59

2 Answers2

2

What you're doing is technically undefined behavior, so the compiler is allowed to do whatever it wants with it.

From what I understand, C structs store their members in memory contiguously.

Not really. But they are stored in the order they are declared.

If I print out the struct's memory I can see that is the case, but it looks like the order of the members is reversed.

That's because you're on a little endian machine. Try to add some more fields after the member_d field. You will likely get the same result. But as I said, it's undefined behavior, so you have no guarantees.

Here is a snippet that illustrates it.

https://onlinegdb.com/uXS2sk142

#include <stdio.h>
#include <stdint.h>
#include <memory.h>

int main(void) {
    int32_t x = 0xDDCCBBAA;
    char p[4];
    memcpy(p, &x, 4);
    
    for(int i=0; i<4; i++) {
        printf("%X", p[i]);
    }
}

It outputs this on a machine with little endian:

AABBCCDD

However, do note that a C compiler is free to add padding. So even if the order is guaranteed in memory, their position is not.

Related:

Detecting endianness programmatically in a C++ program

Structure padding and packing

klutt
  • 30,332
  • 17
  • 55
  • 95
  • Adding too many fields may have the compiler decide to pass the structure as a pointer and change the result. – MikeCAT Jun 10 '21 at 20:47
  • @MikeCAT Clarified a bit about that it's ub – klutt Jun 10 '21 at 20:56
  • klutt thanks for the response. you and @MikeCAT both mentioned (MikeCAT in the other comment section) that what I was doing is "undefined behavior", which answers my question. It sounds like, if I want the members of a struct to be contiguous in memory in a specific order, I need to explicitly copy them to a buffer, I can't rely on the struct itself. – northsideknight Jun 10 '21 at 20:59
  • @northsideknight Something like that – klutt Jun 10 '21 at 21:04
  • No, it is *not* necessarily true that struct members are stored contiguously. – John Bollinger Jun 10 '21 at 21:38
  • 1
    @JohnBollinger Thanks. Fixed it. – klutt Jun 10 '21 at 21:39
1

The important thing to understand here is:

The textual representation of an integer says nothing about its internal representation. It is symbolic.

We accept that without a second thought for textual representations using base 10, or words: 65534 is as symbolic as sixtyfivethousandfivehundredthirtyfour.

But so is 0xfffe. It is a textual representation of an integral value that happens to be written using base 16 instead of base 10. By definition and definition only, borrowing from the decimal notation, digits to the left have higher values. This integral value, 65534, will always be written 0xfffe, no matter which bit pattern realizes it internally. This is one of the most important abstractions that the C programming language provides. It is the reason you always use the shift left operator for multiplication with 2, independent of the bit pattern and order the machine uses.

Any similarity to actual bit patterns, living or dead, is purely coincidental.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62