19 ms·
Stop Memsetting Structures
- anyfoo 8y agoDoes that also zero the padding that may exist between elements of the structure (or at its end)? Because if not, not memset'ing will open you up to all kinds of info leaks, especially if you plan to send that struct over the network (or to another process, or from kernel to userspace...). EDIT: Actually I have to apologize, because I’m not certain what the C standard says about whether the padding bytes can change when changing members of the structure (6.2.6.1): 6 When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. 51) If that means that the padding can actually also change when just assigning individual members (which I think it does, but footnote 51 only calls out copying the whole struct by assignment, and I’m not 100% sure that “including a member object” means what I think in this context), then that means that copying an unpacked structure across information boundaries is technically always unsafe, because any change of a value in the struct might leak information by changing the padding to undefined values. It’s safe with explicitly packed structs, though (which should be the majority of the cases, as you’d have a representation independent of the architecture), but then so is what the author describes. I’d appreciate if someone could clear this up! memset’ing can still help you getting a structure quickly into a well-defined state, though.
- Someone 8y agoSending structures that include padding or that you don’t know the exact layout of over the network is another thing you shouldn’t do, period. Code doing that may break with a compiler change or a compilation flag change on one end of the connection, even if you can guarantee both ends use the same CPU.
- mehrdadn 8y ago> Sending structures that include padding or that you don’t know the exact layout of over the network is another thing you shouldn’t do, period. What if you do know the exact layout and it happens to have a gap between fields?
- noobermin 8y agoTbh I'm not a network person, but my naive brain says instead of casting to char* and writing to the buffer, couldn't you just write out individual fields to a buffer so you control every byte? That seems smarter and safer.
- mehrdadn 8y agoDo you write a separate serialization function for every single struct and sub-struct you send over a network? And then continue maintaining every single one to keep it consistent whenever a field is added/removed/modified in any struct? (Though I should mention that macros actually do make this doable, but they're hardly pretty, and I have yet to see many people embrace this approach, if they realize it's possible at all.)
- kllrnohj 8y ago> Do you write a separate serialization function for every single struct and sub-struct you send over a network? And then continue maintaining every single one to keep it consistent whenever a field is added/removed/modified in any struct? Yes? Of course you do that? Or pick one of a hundred libraries that do that for you - protobuf, cap'n proto, flatbuffers, etc...
- repsilat 8y agoThat's the protobuf approach, but things like Capnp are different, right? Isn't its wire format its in-memory format? I thought it was closer to the "straight memcpy" approach. Protobuf serialization functions can turn into a lot of generated code too (read: big binaries) and stomp all over your icache, because they're different for each type. More "data-driven" approaches using type-generic functions with some runtime type-specific data (like proto descriptors&reflection) can be a good tradeoff for code that isn't as "hot". Typically more branchy than a memcpy or a type-specific function though.
- 8y ago
- CJefferson 8y agoChanging compiler or flags won't change structs, unless you do something crazy. If it did, you couldn't link C libraries compiled with one compiler with programs compiled on another. You can't be sure you can send such structures to different OSes or CPUs however.
- spc476 8y agoIt depends upon the architecture and compiler. One C compiler for the MC68000 might have int as 16 bits (since externally, the MC68000 had a 16-bit external bus) but another one have ints as 32 bits (since the MC68000 can handle 32-bits internally). This was more of an issue during the 80s and 90s when you had more commercial C compiler available than today.
- Gibbon1 8y agoThat is what stdint is for.
- saagarjha 8y ago> If it did, you couldn't link C libraries compiled with one compiler with programs compiled on another. You cannot do this unless the compilers have the same ABI, which is not part of the standard.
- anyfoo 8y agoThere is an attribute that lets you specify alignment per-struct, which means you will know the exact layout.
- hermitdev 8y agoBut it is still potentially problematic if not all of your clients have the same endianess. Memcpy and exact layout still fails you there.
- v_lisivka 8y agoRDMA (Infiniband) does DMA to/from network. Why we should not use RDMA?
- catern 8y agoHmm, that concern is not just limited to RDMA; any kind of shared memory communication across a trust boundary is vulnerable to info leaks in the padding of the struct. That does seem like a real issue. Not sure what's the appropriate mitigation. Having to memset is a drag, and it's easy to forget to do it in some place or another. I'm tempted to say that you should only use packed structs for shared memory communication across trust boundaries.
- gmueckl 8y agoWell, Infiniband is intended mainly for supercomputers. So security is of no real concern within the Imfiniband network as that is implicitly a trusted environment. Access comtrol to that network is the main security boundary. Edit: Also, lots of simulation codes I am aware of tend to send flat arrays anyway. So I'd say that your concerns are mostly theoretical in nature.
- shaklee3 8y agoRDMA, both roce and infiniband have some significant limitations, specifically at higher rates. But they're the best we have right now for direct dma.
- 0x8BADF00D 8y agoSerialization is a non-trivial problem. Especially when dealing with different OS, network stacks, and architectures. protobuf alleviates this quite a bit.
- johncolanduoni 8y agoThis kind of issue happens all the time with structs that a kernel, driver etc. expose to userspace: https://j00ru.vexillium.org/papers/2018/bochspwn_reloaded.pdf https://j00ru.vexillium.org/papers/2018/bochspwn_reloaded.pd... Not sending the structure over the network is insufficient, and rigorous memset is a clear solution to this problem. Also, C structure layout for a specific ABI is strictly determined; if that weren’t the case you couldn’t compile two C compilation units with different flags/compilers and expect them to work.
- blattimwind 8y agoAlso don't expect memset() to actually be a library call at all.
- rurban 8y agoAnd most importantly dont expect memset to be executed at all. Most -O2 compilers happily optimize the memset away if if doesn't care about the sideeffects. Therefore there exists the "secure" memset variants, which are the insecure variants adding a simple compiler barrier, so it isn't optimized away. And there exists the very few really secure memset_s variants with a real memory barrier which guarantees memset to be executed in load and store order, flushing the cache, so that Spectre attacks are mitigated. security-relevant crypto libraries, like openssl and variants don't care about that, because memory barriers are "too slow". only the Linux kernel does it properly. It's still a mess.
- FrozenVoid 8y agoyou can write your own memset that bypasses all of that. Its just 2 lines of code.
- gmueckl 8y agoAnd that will be slower than calling memset() unless you have a really smart compiler.
- 8y ago
- bjourne 8y agoNo, but the follow up article is called Stop Memcpying Structures. And the follow up to that Stop Memcmping Structures. :)
- mehrdadn 8y agoDid you mean this as a joke? I can't find a follow-up article (this one was written today).
- anyfoo 8y agoYou're probably not serious, but why waste time and complexity copying structure elements into a buffer (which likely needs to be memset/bzero'd!), if you can just directly use the struct, all consistent with the standard? C is not a very high-level language. Apart from where undefined behavior is called out, you can and should assume how data is represented on a low level. It was made for writing operating systems and their kernels after all, where interfacing in a bit-accurate way is common.
- bjourne 8y agoI'm joking because endless flame wars have already been spent debating this issue. But I realize not everyone has heard them yet, so let's strap on the football shoes and bring out the horse carcass one more time! There are platforms on which NULL, integer 0 and floating point 0 is not the same. Hence memsetting won't do the right thing. On modern platforms they are, but still, you don't want to get the Standard C Weenies on your back - they're even more annoying than the Rust Evangelism Strike Force. You don't want demons coming out of your nose, do you? :) They do have a point, though. Suppose you are handling a struct you thought were memsetted but was not (your stupid team mate used those damn initializers!), then when serializing it you will get undefined bytes in the stream, possibly triggering hash values to mismatch causing mysterious rebuilds and other stuff. Wrt to field accesses, yes those can overwrite padding bytes. Suppose a is an 8 bit char field, but padded to 4 bytes. Then "foo->a = 22" could be translated to "MOV [RSP+16], EAX" which overwrites the 3 padding bytes with undefined values. The solution, as you mention, is to use packed structs but in those field accesses are much less efficient. Bottom line: code whichever way you want. :) Me, I like to live in the fast lane from time to time and those memset/cpy/cmp functions are sooo handy. :)
- mehrdadn 8y agoNope it doesn't: //GCC: -O0 //MSVC: /Od #include <memory.h> #include <stdio.h> struct S { unsigned char x; int y; }; int f(int f) { S a; memset(&a, f, sizeof(a)); unsigned char y[2]; memcpy(&y, &a, sizeof(y)); return y[1]; } int g() { S a = { 1, 2 }; unsigned char y[2]; memcpy(&y, &a, sizeof(y)); return y[1]; } int main(int argc, char *argv[]) { f(0xDD); printf("%#x\n", g()); f(0xFF); printf("%#x\n", g()); } It definitely seems like the author didn't realize this fact, or I'd have expected it to be addressed in the article.
- unmole 8y agoI am aware of the fact that unitilized padding can have nasty side effects: https://lwn.net/Articles/417989/ https://lwn.net/Articles/417989/ But yeah, I should have mentioned the caveats too. I will update the post once I'm back at my computer.
- mehrdadn 8y agoI'm confused what I'm supposed to look for in that article (do you mean you were the author?) but anyway -- I feel like calling this merely a "caveat" gives the wrong impression? It seems a bit like telling people to mix bleach with vinegar and then saying "Oops, sorry, I did know that it produces chlorine! I forgot to mention that caveat." The caveat isn't just a side note for the margins, it's a critical reason why people avoid this sort of thing.
- Dylan16807 8y agoTechnically I don't think memset forces the compiler to clear the padding and keep it clear either though. The magic of the C abstract machine.
- mehrdadn 8y agoWhy? memset followed by memcpy must return the same byte everywhere. Otherwise what would memset even mean?
- wbl 8y agoPlease explain how you send a struct over the network without UB and leaking the padding.
- anyfoo 8y agoBy knowing its exact size with sizeof(), and using that and a cast to uint8_t* to both bzero() it out before use and memcpy()ing it when sending it. Unless the C standard says that the compiler may change the padding bytes of the existing storage?
- hedora 8y agoTo be clear, it is something like this, which is not bad at all to write: struct st s; bzero(&s, sizeof(s)); // ... write(fd, &s, sizeof(s)); I’ve dealt with many systems that have tons of unnecessary serialization logic, and found them slower and buggier than just letting the C spec define the data format. Having said that, it is possible to get this sort of thing right with C++ and templates (thanks to inlining). Higher-level languages without compile time code generation (like Java and Python) usually make serialization a train wreck in comparison with “send raw structs” or “make the [C++] compiler generate and typecheck the machine code that does the serialization”
- kabdib 8y agoThere's enough wiggle room in the standard that two compilers would be happy making two compliant but still different physical layouts for the same struct declaration. I'm not even worrying about cross-platform issues or bitfields yet. You can get away with directly writing a struct for simple structs, but eventually it will bite you.
- hedora 8y agoPacked structs are reasonably standard (even gcc and visual studio can be made to pack in the same way using the same syntax these days). The remaining issue is endianness, but you can configure ARM to match x86 at boot so shrug. Also, if I tell any reasonably experienced developer my protocol sends a packed struct with these fields: uint8_t version uint16_t reserved uint8_t op int32_t status uint32_t data_length // void data[] They’ll be able to write a correct deserializer in pretty much any language, and can probably write it in ~ 10 lines of code. I agree that packed structs can be tricky, but they’re much better than the vast majority of serialization libraries I’ve dealt with (including all of the open source ones, and I’ve used all the usual suspects).
- jdblair 8y agoBut if you're slamming in-memory structs over the wire, you're using packed structs. You explicitly don't want padding when you use structs that way. Whether you should do this in 2019 is another question, but sometimes it's still the easiest way to implement a binary protocol.
- cardiffspaceman 8y agoAnd you're not writing code for big-endian machines to interoperate with little-endian machines. It's easy to write binary protocol code that way but it isn't portable.
- jdblair 8y agoIt goes without saying that you have get the endian right when you store the values. That's what the endian macros are for.
- thenewwazoo 8y ago> Cue the Rust Evangelism Strike Force chiming in to say that there is really no reason to be writing new C code in 2019. Even as a member of the Force (RIIR 1st brigade), I laughed pretty heartily at this. :)
- _bxg1 8y agoSame. But also, I feel like most of The Force's efforts are directed at C++. I could be wrong.
- jasonhansel 8y agoC++ programmers need to be converted. C programmers just need sympathy.
- deleted 8y ago[deleted]
- nineteen999 8y agoWe don't really need sympathy, we get paid a lot and can afford to drown our sorrows.
- jasonhansel 8y agoAt least until the next zero-day caused by a buffer overrun. (Kidding, kidding.)
- nineteen999 8y agoI know you're kidding, but C programmers also get paid to provide security fixes. There's plenty of C code out there that doesn't interface with untrusted data/input, or does it in a safe way. So I'm not really in fear of losing my job over a simple buffer overflow.
- Gibbon1 8y ago
- jschwartzi 8y agoI'll stop memsetting structures as soon as you support designated initializer lists in C++.
- NikkiA 8y agoApparently it's in C++20, so feel free to stop memsetting whenever you update your C++ compiler next (clang needs a flag to enable it, gcc has had it as part of it's c++2a support for 2 years)
- deleted 8y ago[deleted]
- flohofwoe 8y agoC99 designated init doesn't work for C code that also needs to compile in C++ mode (...yet, a limited subset of designated init is coming in C++20, clang also seems to be more relaxed about this and allows the full C99 designated init also in C++ right now). For code that only needs to compile as C, I agree. It's one of the best (if not the best addition) to the C language. A nice addition would be default values for struct members in the declaration with compile-time constants.
- Someone 8y agoMy C++ is rusty (no pun intended), but if your code also has to compile as C++, I think this will work instead of memset: struct addrinfo hints = {0}; If C++ requires struct addrinfo hints = {}; instead, you could create a macro, and write struct addrinfo hints = ALL_ZEROES;
- loeg 8y agoSure. C++ has initializers ({1,2,3}), it just doesn't have C99 designated initializers ({.a = 1, .b = 2, .c = 3}). This is one of the main pain points around C++ not being a true superset of C.
- hedora 8y agoG++ actually says “sorry” when it bails on that. The other (bigger) pain point is that malloc returns void*, and C++ requires a cast.
- cperciva 8y agoAnother reason to not memset structures: In the code struct foo { void * bar; } baz; ... memset(&baz, 0, sizeof(struct foo)); assert(baz.bar == NULL); it's possible for the assertion to fail, since NULL it not guaranteed to be represented in memory by a zero bytes.
- lpolzer 8y agoYou are right in theory, but I have yet to come upon a compiler that will use something other than zero for null.
- flohofwoe 8y ago...are there any remaining architectures where NULL is not zero? The examples listed here are all historical: http://c-faq.com/null/machexamp.html http://c-faq.com/null/machexamp.html
- ogoffart 8y agoIt does not matter if there are any architectures like that. Relying on that fact is still an undefined behavior. The compiler is allowed to produce any code it wants. If you use memset to initialize structure with pointers with 0 and then test if the pointer are NULL, the compiler could assume that the pointer was not properly initialized and remove the if completely, or actually even remove the whole function.
- flohofwoe 8y agoIn the real world it rarely matter what a theoretical compiler is allowed to do, but what specific compilers actually do. UB only matters when compilers actually "exploit" it.
- CamperBob2 8y agoThat will never happen on current or future platforms, and is not a big concern. Or perhaps it'd be better to say that if it ever does happen on a future platform, then a few memset calls are going to be the least of your problems when porting legacy C code to that platform. However, what has bitten me is memsetting structures that I later turn into full-fledged classes in C++. Oops, there went the VMT. Designated initializers are very nice, as is the ability (in C++) to provide initial-value assignments that run before the constructor.
- HorstG 8y agoThe article gives very dangerous advice, especially for networking code. Padding will not be overwritten and when transmitting the struct (provided there is no attribute packed or the compiler ignores it) information will leak. Yes, someone will point out that there are languages that do not have such problems. However, when still using C, one has to be extremely careful...
- saagarjha 8y agoI am reasonably certain that any code that happens to transmit padding bytes (or observe them in any way) is abusing at the very least implementation-defined behavior.
- heinrichhartman 8y agoAm I the only one who finds this int yes=1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes)) ; much cleaner than this setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &(int) {1}, sizeof(int)); ?
- nocman 8y agoNo, you are not the only one. The author of that code is trying to make it explicit -- "yes, I want to allow reuse of local addressses". Some will prefer this, some won't. I personally like the use of the variable, though I think it's not worth getting all bent out of shape about it one way or another. But I guess we all have our pet peeves, and I know I'm guilty of getting all bent out of shape about things that ultimately don't matter much.
- teej 8y agoCode is read many times more than it is written. If someone scrutinizes code for clear intent, I don’t think that should be considered a “pet peeve” or some quirk to be explained away.
- Gibbon1 8y agoI feel that code that needs to be read a lot is badly named, commented or dodgy.
- nocman 8y agoI wasn't suggesting that any scrutinizing of the code for clear intent should be considered a "pet peeve". I was saying I see both sides in this particular case, and that in my opinion devlopers (myself included) often make a way bigger deal out of things than they really are -- when the larger part of their offense toward a particular choice is often more about personal taste than it is anything else (thus the reference to pet peeves). Again, I like the use of the variable, because in my opinion it makes the code a bit clearer, but I also don't think it is the end of the world if someone chooses the option without it.
- 8y ago
- CJefferson 8y agoThere were a few old free implementations which misbehaved when given null. Maybe they are all gone now, but there was a reason to do it.
- hazeii 8y agoIndeed, I recall getting messages along the lines of "error: freeing a null pointer" or something like that. On the other hand, what are the chances of some freshly-written C being built with an old compiler? (possibly moderately high in the embedded world?).
- edoo 8y agoIf you only use super simple structures maybe. It is quite common to zero out a structure and use only parts of it. It is also quite common to have to pragmatically fill in the structure.
- usefulcat 8y ago> It is quite common to zero out a structure and use only parts of it. That point is addressed in TFA.
- snops 8y agoMy favourite trick with designated initialisers is using them with array indices and enums, e.g. enum color {RED, GREEN, BLUE}; char *color_name[] = { [RED] = "Red", [GREEN] = "Green", [BLUE] = "Blue" }; printf("%s",color_name[RED]); This means you don't have to ensure ordering is the same between the enum and the array, which can be a pain for more complex tables. It's a shame C++ doesn't support this, more details: https://eli.thegreenplace.net/2011/02/15/array-initialization-with-enum-indices-in-c-but-not-c https://eli.thegreenplace.net/2011/02/15/array-initializatio...
- emilfihlman 8y agoInteresting, I was under the impression that compound literals of form (type){value} were illegal and the correct form was (type []){value} but apparently not!
- olliej 8y agoBecause of c spec idiocy it isn’t event sufficient to use memset anymore, you need to use memset_s. But “stop memsetting” is clearly nonsense if you care about security.
- eridius 8y agoSurely you only need memset_s if you're doing this for security reasons, as if memset is optimized away it's because the compiler can prove the observable semantics are the same.
- olliej 8y agoNope, memset will often be “optimized” such that padding is not zeroed. It’s not because the compiler sees that padding isn’t observed, it’s because reading from padding is undefined. Because reading from padding is undefined the write (also undefined) by definition has no observable side effects. Memset_s is the only way to guarantee that padding is actually zeroed, well memset or bzero_s obviously. It’s critically important for people to understand that if the C spec says doing X is undefined, then current compiler writers interpret this as allowing them to do anything they want, even when it clearly introduces security vulnerabilities.
- eridius 8y agoRight, but zeroing padding only matters for security reasons. If your memset isn't being done for security reasons, then memset() is better than memset_s().
- olliej 8y agoIf you are processing data from the network, any uninitialized read gets attacked. My experience is padding (both inside a struct, and outside - array alignment for instance) gets attacked. The only safe option is to initialize everything. I say this having spent more than a decade working on software that is always being attacked.
- seren 8y agoThis is mostly a matter of taste, but if the first example makes sense, the second example looks less clear, because, for someone quickly skimming through the code, a variable which have a somewhat clear meaning "yes" (that could be improved) is replaced with a magic number who have no intrinsic meaning.
- TheSoftwareGuy 8y agoI agree. I’m also not a fan of doing `sizeof(typename)` as it can hide intent
- userbinator 8y agoBecause it’s 2019! It really irritates me when people give arguments like this. I seriously don't care what year it is, I care that the code I write works. A lot of projects still use C89 because they want the widest portability. C99 isn't very new, but especially in the embedded and other niche industries, C89 (often with some extensions) is all you get. That said, "= { 0 };" is almost always usable instead of a memset() and works in C89 too. And finally, there is absolutely no reason to check if a pointer is NULL just before calling free() on it This is advice I agree with. The nullcheck is in free() itself (IMHO a good design decision --- especially along error-handling and cleanup paths.)
- deathanatos 8y ago> but especially in the embedded and other niche industries, C89 (often with some extensions) is all you get. Embedded and niche industries are forcing their programmers to work with 30 year old tooling? (Instead of 20 year tooling?) I think at some point, writing yourself a transpiler from C99 to C89 becomes profitable… (The JS community did it for a lot less than that w/ babel…)
- Gibbon1 8y agoI remember reading a doc's for a library targeting ARM Cortex processors that crowed about it being C89 compatible. I pretty much spit on my keyboard. I also knew the code base was going to be kinda trashy and it was.
- userbinator 8y agoEmbedded and niche industries are forcing their programmers to work with 30 year old tooling? (Instead of 20 year tooling?) Why does how old it is mean anything at all? I personally choose C89 because of its wider compatibility, and I don't think I'm any worse off because of it. As the saying goes, "it's not about the tool, but how you use it." Incidentally, the best programmers I've worked with have also been the ones with the most conservative choice of tools. I think at some point, writing yourself a transpiler from C99 to C89 becomes profitable… (The JS community did it for a lot less than that w/ babel…) If anything, I think the JS community could learn a lot from the "C community" (a very loosely defined term...) --- the huge amount of churn with anything related to JS and web stuff in general turns me off.
- tbodt 8y agoMost interesting to me is that &(int) {1} is equivalent to int one = 1; &one How did I not know about this? I'll be using it from now on.
- seba_dos1 8y ago> Elements that are not specified are initialized as if they are static objects: arithmetic types are initialized to 0; pointers are initialized to NULL. That's actually not true. It is the case when initializing arrays, but for structs (and unions) any non-named member has "indeterminate value" (see paragraph 9, section 6.7.8 of ISO/IEC 9899:1999; unchanged in C11 and C18). Well, at least according to the standard - GCC, Clang and MSVC all happen to make it zero, but it's not guaranteed by the C language. The trick with avoiding a variable when passing an address to a literal value is nice though, I didn't know that. There are some places in my code that could use it ;)
- tom_ 8y agoC11 6.7.9.19 says "all subobjects [of an object initialized with a initializer list] that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration."
- fanf2 8y agoPara 9 is talking about members that do not have names in the struct definition. Missing initializers are covered later: 21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
- paavoova 8y agoInterestingly enough, Clang (but not GCC) appears to embed memset for you despite the code using initializers: https://godbolt.org/z/khJQLV https://godbolt.org/z/khJQLV The author of the article should file a bug report. After all, if they wanted to memset, they'd just do it themselves.
- loeg 8y agoThe compiler knows the representation of NULL on the targeted platform and if it is safe to use memset. It is perfectly valid for the compiler to make this transformation if the platform represents NULL as zero.
- paavoova 8y agoMy post is sarcasm, I just find it humorous that one's efforts to eliminate explicit memset doesn't stop the compiler from going one step backwards and embedding it just the same. If you replace the initialization with memset, it still generates identical code for both Clang and GCC.
- BeeOnRope 8y agoI don't think the point was to eliminate "code that looks like memset at the assembly level", but to eliminate memset at the source level, which the author considers cleaner. Under the covers, one would hope a good optimizing compiler could choose the same assembly for both, which may very well look "memset like" since fewer, wide writes are generally more efficient.
- benou 8y agoFrankly, use the style that is idiomatic to your code base is what is the most important. Keeping things consistent is what matters. And if you start a new project, just use the style you prefer and enforce it :)
- cosmin800 8y agoThe memsetting code is more obvious for older programmers and probably more compatible too. I don't want to start a flame or something, but if you want shiny new programming features and initialisation you should probably look away from C and try javascript ot something similar. One of the reasons we love C is that the same code compiles and works cleanly on the latest linux, a freebsd 4.11. or PIC16.
- nerdile 8y agoI disagree with this author on both counts. 1. Zero filling is theoretically unnecessary, yes. But theory is not often reality. Zero filling is a defensive measure that protects you from other code that makes unhealthy assumptions. Code external to yours may make assumptions about what lies in your padding, either by doing memcmp's, or adding new fields in a later version of the struct and intending it to be ABI compatible between modules. I'll prefer safe and evolvable code over your nitpick about how it's initialized, thank you very much. 2. Consider yourself lucky that you only use free() in your code. In more complex code bases, custom allocators are used. They do not always follow the same semantics as are dictated for free(). Maybe instead of saying "this perfectly valid thing drives me nuts", ask why people do it. It's not because they don't know better.
- iforgotpassword 8y ago> Code external to yours may make assumptions about what lies in your padding, either by doing memcmp's, or adding new fields in a later version of the struct and intending it to be ABI compatible between modules. The content of the padding between members can arbitrarily change during assignments of members, so code relying on the content of it is broken anyways. In the case of an extended struct both methods are equally broken if you don't recompile your module with the updated definition of the struct. Your memset will still have the old size of the struct as the third argument.
- hedora 8y agoIf you use: sizeof(struct_instance) the compiler will automatically update the size passed to memcpy. (I like passing the instance to sizeof and not the type, just in case the type of the thing being copied changes in a future version of the code.)
- iforgotpassword 8y agoThat is exactly what I said: > ...if you don't recompile your module with the updated definition of the struct... And once you recompiled, the C99 version with the pretty initializer is fixed as well, so there is no advantage in using memset regarding this.
- dwrodri 8y agoA new edition of K&R that is edited to address the new features that have been added since C89. I suppose that some might argue that there isn't much need for a new edition because anyone who can grok C89 could probably grok the rest themselves Internet-based self-teaching, but I would pay good money to have all those resources in consolidated into one printed resource.
- childintime 8y agoLast I checked C99 initializers require constants, so they don't generalize, whereas the pattern shown does. I'd prefer if the initializers pattern did allow variables on the right-hand side for this reason.
- atq2119 8y agoNot true, designated initializers expect the same kind of expressions are variable initialization (which is limited at global scope, but arbitrary when initializing local variables).
- ndesaulniers 8y agoOh? I think I reviewed a few Linux kernel patches this week that used memsets to fix up messed up aggregate or designated initialization which is the most concise way of initializing the padding.
- skookumchuck 8y agomemset() heads off the dreaded "I added more fields, but forgot to add the initializers everywhere" bugs. Well, it will still be a bug, but at least it won't be a heisenbug.
- atq2119 8y agoThe point of the article is that C99 initialization does the same.
- deleted 8y ago[deleted]
- swiley 8y agoUgh, what do you do when the structure definition is updated and you now have uninitialized values? If there's no explicit initializer I think memset,calloc or ={0} is just fine thanks.
- hazeii 8y agoUnclear to me why you've been downvoted; I've seen many cases where a structure has had elements added which would be end up uninitialised. A memset(&struct,0,sizeof(struct)) seems pretty reasonable to me.
- NikkiA 8y agoProbably because the article specifically addresses this issue and C99 specifically initializes any members who are not named in the initializer form to either zero or NULL.
- bayareanative 8y agoWe use Crystal for networking code, and it has nice C binding semantics. Haven't had to drop down to pure C much because Crystal just works, and doesn't have the learning curve or un/boxing hoops of rust. It's basically Ruby-like but with rudimentary type-inference, like what Ruby 3 wants to be but Crystal already exists... and orders-of-magnitude more performant.
- vnorilo 8y agoAfter debugging undefined padding bytes breaking per-byte pod type hashes, I'll hang on to my memset, thank you very much! :)
- GuB-42 8y agoIt is C99 code. It is worth noting that many C projects go for extreme portability. That's why C is used in the first place, and sometimes C99 is too much. I still sometimes work with compilers that barely support C89. That's for the aerospace industry BTW. There are even some libc that don't accept free(NULL). If you know the platform you are writing for is not ancient, that's fine but if you are writing portable code, you have to realize these ancient platforms are still alive. I think ditching K&R C is fine though. I've seen some of it but never encountered a case where it was the only thing the compiler supported.
- saagarjha 8y ago> There are even some libc that don't accept free(NULL). That’s pretty much the completely opposite of portable, since it violates the C standard.
- dooglius 8y agoThe "extreme portability" he's talking about is portability of projects to compilers and libc implementations that behave badly, possibly in standards-violating ways like this.
- stochastic_monk 8y ago-Wclass-memaccess is a warning emitted by newer g++ versions. It’s led me to use safer/better, if less convenient, options. (That being said, it’s only applicable to C++.)
- kazinator 8y agoDon't use C99 initializers, or not directly. Write a macro: struct point { double x, y; } #define init_point(x, y) { (x), (y) } /* C90 */ #define init_point(x, y) { .x = (x), .y = (y) } // C99 struct point p = init_point(0.5, 1.5); The macro is superior because macro calls are checked for number of arguments. If you have a structure in which certain members must be initialized (to a non-default value), and such members can be added over time, that will save your butt. How? Because when you add a new member that must not be zero/null, you add a parameter to the macro. Then, the compiler will tell you all the places where the macro is called with too few parameters. Designated initializers eliminate some of the error-prone aspects of C90 style, but don't help enforce the discipline of initializing all members, or a certain subset of members. Functional abstraction with checked arguments beats fancy new syntax.
- kazinator 8y ago> While we’re on the topic of little annoyances, another pattern I often see is using a variable just to pass what is effectively a literal to setsockopt(): int yes=1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (yes)) ; > Which can instead be written as: setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &(int) {1}, sizeof(int)); > I'm sorry, no it cannot. Or, rather, not without 1. Switching C90 code to C99; or 2. Switching C++ code to C99. Good luck with 2. GCC and Clang support this in C++ as an extension; then you're not writing in standard C++ any longer. 1 is a nonstarter in project that mandates C90. In other words, there may be valid reasons why the code is that way other than ignorance of compound literals.