5 ms·
For code that is critical to performance, C99's "flexible array at the end of a struct" is an useful tool. It basically allows you to attach a header at the beg
by segfaultbuserr 4y ago
For code that is critical to performance, C99's "flexible array at the end of a struct" is an useful tool. It basically allows you to attach a header at the beginning of some dynamically-allocated binary data of infinite length (yes, it can be implemented as a pointer at the end of the struct, but the extra latency of another pointer chasing can reduce performance). Before C99, the "size-1 hack" or "size-0 GCC extension" for this purpose was already widespread in both the Linux kernel and Windows [1], but with the disadvantage of triggering memory-safety tools, as the author pointed out.
Meanwhile, unlike C99, this construction is not allowed by any version of the C++ standards, any such use would be a non-standard extension, I think this is unfortunate. I only write C, I wonder if any C++ guru out there can answer this question: does modern C++ have a better solution to implement the same thing?
[1] https://devblogs.microsoft.com/oldnewthing/20040826-00/?p=38043 https://devblogs.microsoft.com/oldnewthing/20040826-00/?p=38...
- quelsolaar 4y agoYou can do it without trailing arrays, by stacking the structures after one and other: MyStructA a; MyStructB b; a = malloc((sizeof a) + (sizeof b)); b = (MyStructB *)&a[1]; You need to make sure that the second struct doesn't have stricter alignment requirements than the one preceding it, but using this technique you can stack any number of structures or arrays of structures in one allocation. (I would generally not recommend this coding style unless you have very specific requirements of memory usage)
- loeg 4y agoThis is pretty similar to: MyStructA { ... MyStructB b[]; }; MyStructA* a = malloc(sizeof(MyStructA) + sizeof(MyStructB)); b = &a->b[0]; (Except, of course, that the syntax for locating 'b' is nicer this way, because you don't have to explicitly address the memory after 'a' and cast it to 'MyStructB'.)
- jcalvinowens 4y ago> Does modern C++ have a better solution to implement the same thing? I'm no guru, but I know from experience you can do it in C++: {0}[calvin ~] cat test.cpp #include <iostream> #include <memory> struct foo { int len; int v[]; }; int main(void) { auto p = std::unique_ptr<foo>(reinterpret_cast<struct foo *>( malloc(sizeof(struct foo) + sizeof(int) * 2))); p->v[1] = 99; std::cerr << p->v[1] << std::endl; return 0; } {0}[calvin ~] g++ -Wall -Wextra -std=c++17 test.cpp -o test {0}[calvin ~] ./test 99 {0}[calvin ~] clang++ -Wall -Wextra -std=c++17 test.cpp -o test {0}[calvin ~] ./test 99 EDIT: Remove unnecessary extern block, as pointed out by wahern in the replies.
- loeg 4y agoYes, you can do this in C++, but keep in mind that implicitly generated copy/move constructors don't understand this and will not copy the full object. This can produce surprising memory corruption that can be difficult to debug. So you should be sure to either explicitly mark the struct not copy/movable, or implement smarter copy/move operators.
- _gabe_ 4y agoTbf I don't think C copies the whole flexible array member either[0]. So the care you would normally take while using a flexible array member in C should also be taken in C++ :) [0]: https://stackoverflow.com/questions/35423293/flexible-array-member-not-getting-copied-when-i-make-a-shallow-copy-of-a-struct https://stackoverflow.com/questions/35423293/flexible-array-...
- loeg 4y agoRight. But C has fewer scenarios where implicit copies are possible, in part because it does not have references.
- wahern 4y agoFlexible array members are commonly supported as an extension in C++ compilers, but the C++ standard itself does not permit them. And FWIW `extern "C"` doesn't drop a C++ compiler into a "C mode", it merely effects linkage (i.e. name mangling); all the code within the scope must still be valid, compiler-supported C++. Your example code compiles the same without the extern "C" declaration. And it compiles the same if main is also placed within the extern "C" scope; but not the headers, as C++ constructs like templates cannot have C linkage.
- jcalvinowens 4y agoD'oh, thanks for pointing that out.
- masklinn 4y ago
- hn_go_brrrrr 4y agoNo, C++ has nothing better.
- cozzyd 4y agohere's a worse way that's a lot more work and maybe has some advantages: https://gist.github.com/cozzyd/efda739301bb7eb3a4a63a145c93ef3a https://gist.github.com/cozzyd/efda739301bb7eb3a4a63a145c93e...
- jancsika 4y ago> yes, it can be implemented as a pointer at the end of the struct, but the extra latency of another pointer chasing can reduce performance For the latency to be significant digits, there must be a lot of repeated calls which only read/write a very small number of elements in the array. Otherwise the accumulation of read/write operations performed when iterating over the data would dwarf the single pointer dereference. So I'm curious now-- do OS kernels spend most of their time doing lots of calls that dive into such dynamically-allocated data just to extract a single datum or two?
- deleted 4y ago[deleted]
- pjmlp 4y agoBecause C++ has offered better alternatives for bounds checked data structures since it exists.
- planede 4y ago> does modern C++ have a better solution to implement the same thing? No, the only standard way is to allocate a buffer large enough, and placement new the header and the bulk payload separately. The manual handling of alignment makes this very cumbersome. Flexible array members would be nice, but I don't like that it is yet an other overload on some array declaration syntax (other meanings of "T x[]": 1) declare an array of unknown size, 2) in a definition, deduce the size). For some reason C likes to overload the array declaration syntax with widely different meanings (looking at you, VLAs).