Live data from Hacker News

The Lost Art of Structure Packing (2018)

catb.org

51–60 of 120 posts

Re: The Lost Art of Structure Packing (2018)

#51
I always assumed that in C, a given structure would always have the same memory layout, no matter what the compiler is (as long as the compilers target the same architecture of course).

I always assumed that in C++, the memory layout could change a lot between compilers (the location of the pointer to the vtable for example). Do you know if it's true, and if the layout do change, could you give an example?

Re: The Lost Art of Structure Packing (2018)

#52
post #51

I always assumed that in C, a given structure would always have the same memory layout, no matter what the compiler is (as long as the compilers target the same architecture of course). I always assumed that in C++, the memory layout could change a lot between compilers (the location of the pointer to the vtable for example). Do you know if it's true, and if the layout do change, could you give an example?

> I always assumed that in C, a given structure would always have the same memory layout, no matter what the compiler is (as long as the compilers target the same architecture of course).

Usually, but not always. In most cases there is one efficient way to pack the structure and still maintain member alignment, but there's not requirement that the amount of padding looks like this.

> I always assumed that in C++, the memory layout could change a lot between compilers (the location of the pointer to the vtable for example). Do you know if it's true, and if the layout do change, could you give an example?

Yes, once you have a non-POD type the memory layout can be fairly arbitrary as you cannot really inspect it and compilers are free to lay it out as they wish.

Re: The Lost Art of Structure Packing (2018)

#53
post #51

I always assumed that in C, a given structure would always have the same memory layout, no matter what the compiler is (as long as the compilers target the same architecture of course). I always assumed that in C++, the memory layout could change a lot between compilers (the location of the pointer to the vtable for example). Do you know if it's true, and if the layout do change, could you give an example?

Most platforms have have system level C APIs that are exposed to userspace. When using these APIs it's necessary to layout structures as they expect. Therefore all compilers on the same platform (OS+arch) would usually produce the same layout for structs so that they are compatible.

However this isn't universally true. Some platforms might not have a well defined C ABI.

Re: The Lost Art of Structure Packing (2018)

#54
post #41

Earlier quoted context omitted.

If you rely on code containing undefined behaviour you're in for a world of butthurt sooner rather than later. There is no way in C++ to get at the padding bytes unless you're using undefined behaviour. How does the hash function work? Pointer aliasing using reinterpret_cast? Pointer aliasing using C-style casts? Typing punning through the old union switcheroo?

I don't have the code in front of me, but something like int hash=0; for(int i=0;i (&obj)+i); return hash; Basically hashing each byte of the memory containing the object, regardless of what the object itself represents. We can argue whether that's a smart thing to do or not, but I wasn't in charge of implementing it - it's a relic from a codebase that's more than a decade old at this point. It's a simple hashing met…

Yeah, this is technically undefined.

Re: The Lost Art of Structure Packing (2018)

#55
post #42

Earlier quoted context omitted.

memset, sure, then you copy the struct (return it or pass it by value) and the compiler won't bother copying the padding bytes. Or worse, an optimizing compiler will see that you're writing to padding bytes and helpfully no-op it.

First of all, there is no way the compiler is going to optimize a call to memset(&foo, 0, sizeof(foo)) when &foo is being interpreted as a void pointer. That doesn't even make sense. Second of all, in a generic C interface keys are likely to be treated as void pointer and almost certainly are going to be moved around with memcpy etc. rather than returned/passed by value since doing so would make the interface non-gen…

The compiler knows what memset does. One of the earliest steps of optimization is replacing calls to well-known functions with intrinsics. Try it! https://godbolt.org/z/QUREQi

Yes, it's true that generic C code will type-erase the key type. However it just takes a little refactoring in specific code to move the struct initialization across a call boundary from where it is passed to the generic code.

Re: The Lost Art of Structure Packing (2018)

#56

Earlier quoted context omitted.

I don’t think memset is required to zero out padding bytes. The correct way to do this is to only access non-padding bytes.

memset is absolutely required to zero out padding bytes. Padding is part of the structure size, and memset hast to zero out exactly as many bytes as it is told. memseting a structure to zero is commonly done in programs that send structure outside of the process (like passing it to communication or storage-related system calls) because the padding can leak sensitive information. That better work! If the size of a str…

memset is not required to do anything if the side effects are not observable; this is the entire reason why memset_s needed to be added (and also why __attribute__((packed)) is recommended for anything that is being sent directly over the wire, if this kind of construction cannot be avoided). Compilers often inline and unroll small, constant-size memsets anyways, and since it is not possible to observe a consistent padding in C in a standards-compliant way to my knowledge, an optimizing compiler should be able to legally leave those bits alone.

Re: The Lost Art of Structure Packing (2018)

#57
post #42

Earlier quoted context omitted.

memset, sure, then you copy the struct (return it or pass it by value) and the compiler won't bother copying the padding bytes. Or worse, an optimizing compiler will see that you're writing to padding bytes and helpfully no-op it.

First of all, there is no way the compiler is going to optimize a call to memset(&foo, 0, sizeof(foo)) when &foo is being interpreted as a void pointer. That doesn't even make sense. Second of all, in a generic C interface keys are likely to be treated as void pointer and almost certainly are going to be moved around with memcpy etc. rather than returned/passed by value since doing so would make the interface non-gen…

> First of all, there is no way the compiler is going to optimize a call to memset(&foo, 0, sizeof(foo)) when &foo is being interpreted as a void pointer. That doesn't even make sense.

that is really dangerous to assume. memset is a compiler built-in in every relevant C++ compiler and the compiler definitely knows the type of the object that is behind your void* and knows if you're being nasty.

e.g. look at this code : https://gcc.godbolt.org/z/xh9BXs

it's UB, and the compiler knows it and inserts an "invalid opcode" instruction even if you try to hide a memset behind a void*-taking function

Re: The Lost Art of Structure Packing (2018)

#58
post #32

Earlier quoted context omitted.

I don’t think memset is required to zero out padding bytes. The correct way to do this is to only access non-padding bytes.

sizeof(struct foo) is required to return the actual size, including padding, so struct foo foo_inst; memset(&foo_inst, 0, sizeof(foo_inst)); will certainly result in all padding bytes being set to zero.

sizeof will include padding, but there is no need for memset to actually perform a write that cannot be consistently observed.

Re: The Lost Art of Structure Packing (2018)

#59

Earlier quoted context omitted.

memset, sure, then you copy the struct (return it or pass it by value) and the compiler won't bother copying the padding bytes. Or worse, an optimizing compiler will see that you're writing to padding bytes and helpfully no-op it.

A compiler that optimizes away memset is not one that could be used for targeting anything that sits on a network.

That immediately rules out GCC and Clang…

Re: The Lost Art of Structure Packing (2018)

#60

Earlier quoted context omitted.

I work in video games, and very recently we had a sneaky bug in one of our AAA titles(that was already out!), where(in huge simplification) we had a struct that looked like: struct Obj { int foo; bool bar; } then we were storing those in a custom hashmap using these as keys, where the hashing function was basically hashing bits of each stored object, without any awareness of what's in the object. The bug was found wh…

> C++20 now has a "hashable" concept to help with this I'm sorry, you can easily solve this problem in K&R C from 1979.

That's true but missing the point.

The point is that C++20 adds a way to catch yourself before you make the exact type of mistake the grandparent comment talks about.

https://en.cppreference.com/w/cpp/language/constraints

Post reply on HN