14 ms·
My personal C coding style as of late 2023
- david2ndaccount 3y agoI disagree about the structs vs out-parameters thing. I’ve found it makes functions that could return an error much harder to compose and leads to a proliferation of types all over the place. In practice almost all functions can fail (assuming you are handling OOM), so having a predictable style of returning errors is more important.
- staunton 3y ago> assuming you are handling OOM Which almost noone ever does. It's very hard and almost never has any benefit. At that point you have way different problems than programming style choices...
- JonChesterfield 3y agoReturning option<foo> (or sum<foo, error>) is the right thing but a real pain to write in C. I'm not sure the pattern of `if (thing(...)) goto fail` on every function call is particularly wonderful either, though the Go crowd seem to like it. Otherwise there's thread_local mylibrary_errno, which might actually be the right thing for within a library, translating it to an enum return on the boundaries.
- kamray23 3y agoYou'd get normal errno/out param semantics if you had access to semantic struct unpacking. But you don't. Even then, composing optional values in C has always been a bit of a pain. If you're not doing exceptions, your two choices seem to be exceptions and monads in every language, and neither work in C, or would even be compatible with the philosophy that most C programmers have. I guess you could attempt to pull some kind of macro but it only works on simple one-to-the-other calls. C++ optionals, as terrible as C++ is, are certainly more fun to use than if(foo(x,y, out1, out2) != WHATEVER_LIBRARY_OK) { ... }
- Gibbon1 3y ago[flagged]
- petabytes 3y agoWhat's wrong with it?
- superchroma 3y agoWell, from a team perspective, it's extremely opinionated and hostile to newcomers and messes with core language features at the expense of readability. If it's your personal codebase then do whatever, obviously.
- aportnoy 3y ago> extremely opinionated I have not seen a single codebase that widely uses uint8_t and does not typedef it to u8. It is the exact opposite of "extremely opinionated".
- loeg 3y agoIt doesn't mess with a core language feature to alias 'u8' to 'uint8_t'. It's a reasonable use for the name and one used in other languages (e.g., Rust). There's nothing in the C standard that defines or uses the 'u8' name.
- Dylan16807 3y agoOpinionated? Was there something else you wanted u8 to mean?
- dmr_92 3y agoCan you explain why---what is it about this style that puts you off?
- antiquark 3y ago> #define sizeof(x) (size)sizeof(x) Technically, it's illegal to #define over a language keyword.
- chii 3y agoreally? then how do you #define things like types like `int` and `char`?
- antiquark 3y agoFor example, it would be illegal to do the following: > #define int long Because you're replacing the int keyword with something else. The standard says: > 17.6.4.3.1 [macro.names] paragraph 2: A translation unit shall not #define or #undef names lexically identical to keywords, to the identifiers listed in Table 3, or to the attribute-tokens described in 7.6.
- jenadine 3y agoThat's C++. But I couldn't find the same restriction in C. In fact it seems that C allows it as long as you don't include any of the standard C header. > 7.1.2 "Standard headers" §5 [...] The program shall not have any macros with names lexically identical to keywords currently defined prior to the inclusion of the header or when any macro defined in the header is expanded.
- Joker_vD 3y agoAnd so you still can re-define keywords, but only after you've included all the standard headers you want. Which makes sense: the meaning of including a standard header is entirely standard-mandated (they are not even required to be actual files) so making anything that could potentially mess with the implementation's implementation of standard headers UB is reasonable.
- antiquark 3y agoSearching around gave me this from stackoverflow: In C standard, it is not allowed. > C11, § 6.4.1 Keywords > [...] > The above tokens (case sensitive) are reserved (in translation phases 7 and 8) for use as keywords, and shall not be used otherwise. https://stackoverflow.com/questions/12286691/keywords-redefinition-in-c-c https://stackoverflow.com/questions/12286691/keywords-redefi...
- MBCook 3y agoA lot of this makes sense to me. I’ve started writing a bare metal OS for Arm64. It’s very early but I’ve done some similar things. I’m using pascal strings, I’ve also renamed the types (though I’m using “int8” style, not “i8”). I quickly decided that I never intend to port real software to it, so I really don’t have to conform to standard C library functions or conventions. That’s given me more freedom to play around. C is old enough to have a lot of baggage from when every byte was precious, even in function names. It’s nice to get away from that. Much like the contents of this post, that plus other small renamed just ended up feeling like a nice cleanup.
- eddd-ddde 3y agoi never thought about that, saving bytes even in symbols
- Eggsellence 3y agoI think he is suggesting the opposite - use more verbose names for clarity.
- trealira 3y agoYeah, old C compilers would only look at the first 6 characters of a name, and the rest were insignificant. That's how you get nanrs like "strcpy" and "malloc" instead of something like "string_copy" or "mem_allocate" (I still think "memory_allocate" would be long enough to be annoying to type).
- lifthrasiir 3y agoOne of last vestiges of this fact AFAIK was libjpeg, which had a macro NEED_SHORT_EXTERNAL_NAMES that shortens all public identifiers to have unique 6-letter-long prefixes. Libjpeg-turbo nowadays has removed them though [1]. [1] https://github.com/libjpeg-turbo/libjpeg-turbo/commit/52ded8768022ab7e9e7670ddea0ba89e2890b837 https://github.com/libjpeg-turbo/libjpeg-turbo/commit/52ded8...
- nxobject 3y ago
- ForkMeOnTinder 3y ago> To beginners it might seem like “wasting memory” by using a 32-bit boolean Maybe I'm a beginner then. He lists a few cases where it's not worse than sticking to 8-bit bools, but no cases where it's actually an improvement. It still wastes memory sometimes, e.g. if you have adjacent booleans in a struct, or boolean variables in a function that spill out of registers onto the stack. Sure it's only a few bytes here and there, but why pessimize? What do you gain from using a larger size?
- Quekid5 3y agoComputer architecture is optimized for 32+ bit aligned access to most things. The gain is (usually, but not always!) performance.
- gavinhoward 3y agoI'm afraid you are only slightly correct. Architectures are generally optimized for aligned access (or disallow unaligned access), but what counts as "aligned" is different for each type. A char type that is used for a bool can be accessed on any byte boundary because the alignment of a char is 1. The alignment of a 32-bit value is 4. However, architectures are generally more optimized for 32-bit operations in registers. If you're dealing with a char in a register, the compiler will generally treat it as a 32-bit value, clearing the top bits. (This is one of those places where C's UB can bite you.) However, there are architectures where 32-bit access is optimized.
- deleted 3y ago[deleted]
- tedunangst 3y agoWhat's an example of a function where a boolean variable spills to the stack and the 3 bytes are important?
- gavinhoward 3y agoAnd to add to that, if you use an actual bool type, sanitizers will warn you if they are ever any value other than false (0) or true (1).
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- cshenton 3y agoReally lovely. A lot here reminds me of design in Odin lang. Short integral types, no const, composite returns over out params. Big fan of the approach of designing for a single translation unit and exploiting the optimisations that provides from RVO etc.
- im3w1l 3y ago> #define assert(c) while (!(c)) __builtin_unreachable() This seems like a bad idea, because the whole point of an assert is that something shouldn't happen, but might due to a (future?) bug.
- haimez 3y ago> This seems like a bad idea, because the whole point of an assert is that something shouldn't happen, but might due to a (future?) bug. And so it’s a bad idea because…? The whole idea is to notice a bug before it ships. Asserts are usually enabled in test and debug builds. So having an assert hit the “unreachable” path should be a good way to notice “hey, you’ve achieved the unexpected” in a bad way. You’re going to need to clarify in more detail why you think that’s a bad thing. I’m guessing because you would prefer this to be a real runtime check in non debug builds?
- im3w1l 3y agoIt's undefined behavior if the assert triggers in production. It's too greedy for minor performance benefit at the risk of causing strange issues.
- haimez 3y agoYikes. I did have to go down a little rabbit hole to understand the semantics of that builtin (I don’t normally write C if that wasn’t immediately obvious from the question) but that seems like a really questionable interpretation of “this should never happen”. I would expect the equivalent of a fault being triggered and termination of the program, but I guess this is what the legacy of intentionally obtuse undefined behavior handling in compilers gets you.
- im3w1l 3y agoThe builtin itself is fine. It works exactly as it's intended. It says "I've double and tripple checked this. Trust me compiler. Just go fast". But you should not use it to construct an assert.
- ww520 3y agoThis is an excellent writeup. I especially like the string treatment and the fat-pointer struct parameters and return values.
- bsder 3y ago> signed sizes are the way Well, I should probably just say "We're done here." and stop reading the rest of the article. "Signed sizes" are an extremely surprising abstraction break that are just asking for disaster. > No const. It serves no practical role in optimization, and I cannot recall an instance where it caught, or would have caught, a mistake. Should you even be writing C if you haven't hit this? People mix up "in buffers" and "out buffers" all the time. "const" flags this immediately. > Declare all functions static except for entry points. Again, with everything compiled as a single translation unit there’s no reason to do otherwise. And when you go trying to debug something and get at a variable or function that you can't find because everything is "static", you'll curse the one who wrote the code. > Another change has been preferring structure returns instead of out parameters. Which is a great way to accidentally return a pointer to your stack and open a big ass security hole. Passing in the output buffers makes clear the ownership semantics. This guy seems like he mostly writes code for 64-bit systems. The coding advice is ... okay, I guess? Maybe? In that domain? In a 32-bit embedded domain, some of these guidelines are a good way to get youself into a lot of trouble in a real hurry.
- tom_ 3y agoThe no-const people will never be satisfied, so just use const as necessary, propagate as required, and ignore them when they complain. If they take it out, put it back in. They'll always get bored first. I've been doing this for 25 years, and I'm still here. (The static thing might depend on the tooling. I went static-by-default about 15 years ago, around the same time I went full size_t, and I've yet to have a problem with it.)
- thefourthchime 3y agoI’ve been her 30 years. I’ve never found much use for const. I value brief simple code that doesn’t rely on things like const to tell people what’s going on. Codebases have their own conventions and design patterns. If you have that const is a needless formality. Code should be being simple and clean first, constantly stating things that are obvious 90% of the time isn’t that.
- 3y ago
- ComputerGuru 3y agoI might just be a grumpy old dev but a lot of this stuff gets an immediate no from me because it’s so unidiomatic. You have to unlearn the accepted way of doing things and you end up with a codebase that is just so foreign to anyone looking at even a small chunk of it, unless they are committed to really learning to do things your way. Everyone knows what a uint32_t is when they see it. The cognitive overhead (until it becomes second nature, obviously) just feels like a heavy price to pay in order to save yourself a few characters. (Some other stuff in the proposed coding style still gets a thumbs up from me, though.)
- Pannoniae 3y agoUnpopular opinion: something being unusual does not necessarily mean it is bad. Yes, it will look foreign to random people looking at it, but if someone wants to seriously work with it, it will only take a few days to get familiarised with it. The justification of "cognitive overhead" is, from what I have seen, a shibboleth for rejecting "outsider" code written by someone not conforming to the language standards by claiming it is harder to understand. Personally, I would say that says more about the person's inflexibility and/or OCD, not the writer's style. I am not saying every style is good (some simply obfuscate things and/or make things overly verbose or unreadable) but rejecting a style solely based on it being "non-idiomatic" is not a good thing.
- thetic 3y agoI don't even think that's a controversial opinion. Breaking convention isn't inherently bad; it has costs, some of which you described. In this case specifically, the novelty is not justified by any significant benefit.
- yura 3y agoWell said. This is also something that I don't buy from the criticism towards Lisp. Something along the lines of: "Lisp did not become mainstream because everyone writes their own little language for their project, and so no one can understand other project's code." pg wrote excellent arguments against this criticism in "On Lisp" § 4.8 Density, which apply just as well to the discussion above: “If your code uses a lot of new utilities, some readers may complain that it is hard to understand. People who are not yet very fluent in Lisp will only be used to reading raw Lisp. In fact, they may not be used to the idea of an extensible language at all. When they look at a program which depends heavily on utilities, it may seem to them that the author has, out of pure eccentricity, decided to write the program in some sort of private language. [...] If people complain that using utilities makes your code hard to read, they probably don’t realize what the code would look like if you hadn’t used them. Bottom-up programming makes what would otherwise be a large program look like a small, simple one. This can give the impression that the program doesn’t do much, and should therefore be easy to read. When inexperienced readers look closer and find that this isn’t so, they react with dismay.”
- hyc_symas 3y agotypedef all structs - yes, helps with conciseness. Use typedefs liberally, I say. But only typedef the things themselves, not pointers to the things. You can always use (type *) when you need a pointer. In particular, for function pointers, typedef the function, not the function pointer. Then you can use the function typedef for function declarations too, which gives you parameter type checking without needing to fix declarations everywhere if you change a function signature. I see most C codebases get this one wrong, typedef'ing the function pointer and still needing to manually write out all function declarations for that pointer definition. I'm not sold on the structs as return types thing. I prefer just a numeric error code as a return value, and out parameters for any other returns.
- jpcfl 3y agoI prefer to use typedef's for opaque structs to emulate classes with all private fields, and use 'struct' for plain ol' data structures. Classes should only be accessed via functions, while structs can be accessed directly. I think this is more-or-less a C/POSIX standard convention. E.g., `pthread_t` vs. `struct stat`.
- lelanthran 3y ago> I prefer to use typedef's for opaque structs to emulate classes with all private fields, and use 'struct' for plain ol' data structures. Classes should only be accessed via functions, while structs can be accessed directly. Totally agree, I even wrote this as a blog post: https://www.lelanthran.com/chap9/content.html https://www.lelanthran.com/chap9/content.html
- yvdriess 3y agoIt's definitely part of the linux kernel coding style: https://www.kernel.org/doc/html/v4.10/process/coding-style.html#typedefs https://www.kernel.org/doc/html/v4.10/process/coding-style.h...
- Warwolt 3y agoThat's all fine, but you cannot have nicely behaved stack allocated structs and use the data hiding method outlined in that blog post, which I think is a pretty big caveat
- comex 3y ago> #define sizeof(x) (size)sizeof(x) I'm guessing this is lacking an outer pair of parentheses (i.e. it's not `((size)sizeof(x))`) on the grounds that they're unnecessary. In terms of operator precedence, casting binds tightly, so if you write e.g. `sizeof(x) * 3`, it expands to `(size)sizeof(x) * 3`, which is equivalent to `((size)sizeof(x)) * 3`: the cast happens before the multiplication. Indeed, casting binds more tightly than anything that could appear on the right of sizeof(x) – with one exception which is completely trivial. But just for fun, I'll point out the exception. It's this: (size)sizeof(x)[y] Indexing binds more tightly than casting, so the indexing happens before the cast. In other words, it's equivalent to `(size)(sizeof(x)[y])`, not `((size)sizeof(x))[y]`. But you would never see that in a real program, since the size of something is not a pointer or array that can be indexed. Except that technically, C allows you to write integer[pointer], with the same meaning as pointer[integer]. Not that anyone ever writes code like that intentionally. But you could. And if you do, it will compile and do the wrong thing, thanks to the macro lacking the extra parentheses. …On a more substantive note, I quite disagree with the claim that signed sizes are better. If you click through to the previous arena allocator post, the author says that unsigned sizes are a "source of defects" and in particular the code he presents would have a defect if you changed the signed types to unsigned. Which is true – but the code as presented also has a bug! Namely, it will corrupt memory if `count` is negative. You could argue that the code is correct as long as the arguments are valid, but it's very easy for overflow elsewhere in the code to make something accidentally go negative, so it's better for an allocator not to exacerbate the issue. With unsigned integers, a negative count is not even representable, and a similar overflow elsewhere in the program would instead give you an extremely high positive count, which the code already checks for. Personally I prefer to use unsigned integers but do as much as possible with bounds-checked wrappers that abort on overflow. Rarely does the performance difference actually matter.
- bjourne 3y agoThat's a good catch. The moral of the story is that unless your macro definition expands to a single token (e.g #define X 123) you should always, always, always surround it with parenthesis. Because C's precedence rules are damn complicated.
- jeanlucas 3y agoI wish I could work with C again
- Quekid5 3y agoI wish nobody would have to. (Haha, only serious.)
- deleted 3y ago[deleted]
- sys_64738 3y agoC is wonderful so if you an find a project at work with a lot of C code then it'll remain forever. All these other fad languages will die before C ever does.
- josephg 3y agoI'm hopeful for Zig as a modern replacement for C. It feels modern like rust, but still with C's lightness.
- thetic 3y ago> #define sizeof(x) (size)sizeof(x) Undefined behavior[1] > #define assert(c) while (!(c)) __builtin_unreachable() Undefined behavior[1] > I’ll cast away the const if needed. Undefined behavior[2] > The assignments are separated by sequence points, giving them an explicit order. I don't believe assignments are sequence points and only the function call is. [1] https://en.cppreference.com/w/c/language/identifier#Reserved_identifiers https://en.cppreference.com/w/c/language/identifier#Reserved... [2] https://en.cppreference.com/w/c/language/const https://en.cppreference.com/w/c/language/const
- WalterBright 3y ago> #define assert(c) while (!(c)) __builtin_unreachable() And people keep telling me that nobody uses the C preprocessor to define their own syntax any more!
- dundarious 3y ago> > I’ll cast away the const if needed. > Undefined behavior[2] To be clear, it's only UB if the object was defined const, which is the case given he wrote: > One small exception: I still like it as a hint to place static tables in read-only memory closer to the code. I’ll cast away the const if needed. So you are correct on this point. Funnily enough, such objects are relatively rare IME, so I had to double-check to see that he was advocating it specifically in the rare case where it must not be applied.
- comex 3y ago> Undefined behavior[2] Given that this particular undefined behavior usually causes crashes in practice, I expect the author is talking about casting away the const but not actually writing to the pointer. Which is legal.
- paulddraper 3y agoOr a common situation is mutable -> const -> mutable. And that is legal.
- thetic 3y ago
- bjourne 3y agoI like it a lot. Especially the part about ditching const qualifiers. They clutter function declarations, don't make the intent any clear, and almost never improve performance. Restrict, on the other hand, I've found makes the compilers emit better code in many cases. But I don't like using 1 and 0 instead of booleans. Many standard C functions (fclose for example), return 0 on success. Better to be explicit here.
- pylua 3y agoI like using the const keyword, and believe it serves a real purpose with readability. I feel like most things are read access by default which is why it seems cluttered. I believe rust gets immutable by default correct .
- dundarious 3y agoI use an exitint typedef to signify "0 is success, non-0 is failure" and boolint equivalent to his b32. Not typesafe of course, so it's just info for fallible humans.
- drpixie 3y agoWhile there are a few disagreeable points, I like the article. I've always felt that C is unfairly maligned. Yes, it's very low level, it's meant to be. Yes, it lets you shoot yourself in the foot, but what language doesn't? Most of the problems with C are really issues with the standard library, the Unix (now Posix) interfaces, and the string type. None of these are actually part of C, but are part of how C is normally used. So those problems can be avoided, and use C for what it's good at.
- edvinbesic 3y ago> I've always felt that C is unfairly maligned. Yes, it's very low level, it's meant to be. Yes, it lets you shoot yourself in the foot, but what language doesn't Isn’t it a beauty of lower level languages that creating higher level abstractions provides more value? edit: typo
- eldenring 3y ago> Yes, it lets you shoot yourself in the foot, but what language doesn't? Good lord.
- drpixie 3y agoWell ... Name a language is which you categorically cannot "shoot yourself in the foot"!
- jcrites 3y agoThe issue is that it's a spectrum: how easily you can shoot yourself in the foot, especially on accident, without awareness of the risks. And perhaps what the consequences are when you do. Risk and consequence. C is high risk and also high consequence. In higher level languages, you can't shoot yourself in the foot nearly as easily in such a way as to trivially create a correctness problem and security vulnerability (like a buffer under/overflow). Languages like Java and C# make it pretty difficult to shoot yourself in the foot this way (though you still can in other ways, like with incorrect concurrency). Rust makes it a lot harder to shoot yourself in the foot across the board, especially on accident (i.e., without being aware that you're something dangerous and low-level, viz. `unsafe`).
- WalterBright 3y agoInteresting how my experience has led me in a different direction: https://dlang.org/blog/2023/10/02/crafting-self-evident-code-with-d/ https://dlang.org/blog/2023/10/02/crafting-self-evident-code... (The article is crafted around D, but the principles apply to C as well.)
- keyle 3y agoFun read. What happened to the conditional expressions? Move them to the interiors of doX() and doZ(). That was an interesting point. Not sure that it's always valid but I guess it depends where you want the abstraction to lay, and how it affects the mental construct around the code. e.g. deleteRecords(); is not better than if let x = deadRecords() deleteRecords(x); Sure, it looks messier but there is value is showing upfront that you're pruning and not wiping. If the author wisely renames his function e.g. pruneDeadProjects(), yes. But merely moving the the condition within the function can be dangerous for context and be a leaky abstraction.
- WalterBright 3y agoFinding the right abstraction isn't always easy. Sometimes if I just put it down and let it slosh around in my brain for a few days, it comes to me. Like your idea of pruneDeadProjects()!
- tmtvl 3y agoThe programming strategy of "less typing, more thinking", it's a good one for avoiding RSI.
- juunpp 3y agoHow do you not use const for arguments of data types that are too costly to pass by value?
- progfix 3y agoUsually if it is too costly, then you pass it via a pointer.
- jpcfl 3y agoParameters and functions No const. Please don't. `const` is incredibly valuable, not only to the reader, but to the compiler. Take for example: int Foo_bar(Foo const* self); Just looking at this signature, I know that calling `bar()` will not modify the state of the object. This is incredibly valuable information to the reader. Furthermore, if I want to create a `Foo` constant, I can only call this function if it is `const`. static Foo const a_foo = FOO_INIT(&some_params); return Foo_bar(&a_foo); // Will not compile without 'const' in function `const` is valuable to the compiler, since `a_foo` can be placed into ROM on some platforms like MCUs, saving precious RAM.
- paulddraper 3y agoAgreed; const is one of those features that is so good I wish a lot of other languages (e.g. java) had it.
- epcoa 3y agoconst in C and C++ are an abomination. On a pointer they don’t tell the compiler to do shit, because they can’t. That I can agree with TFA. However I agree with the GP that dismissing it entirely is a little misplaced. It serves as a hint/documentation and I think the article undersells the value of rodata (not the pointer use of const which is basically shit). I mean I have seen at least a few SIGSEGV/aborts due to attempted writes to ro memory. Also like, one of the few modern justifications for C, embedded, const still has important link time meaning.
- skovati 3y agoFWIW, Java does have the "final" keyword.
- flakes 3y agoFinal only protects the variable from being assigned a new reference (similar to a const pointer). It doesn’t protect any of the underlying data held by the object from being changed, unless the entire hierarchy has every field declared final as well. I still use final heavily in all of my Java code, but it doesnt convey the full intent I would like it to.
- Uptrenda 3y agoIMO, defining your own types is one step too far. Now everyone who is already familiar with C types has to learn your own quirky system to understand one program. I think it does probably make sense to be specific about the sizes though e.g. using uint32_t over just uint (and expecting to receive some architecture-dependent size you might not get with uint.) These types should be defined in the right header (I think it depends on compiler?) It's been a while since I wrote any amount of C so my apologizes if this isn't correct.
- voxl 3y agothey're not quirky types in the least...
- guidoism 3y agoI agree. A lot of languages have settled on those same names or something similar. We don’t live in a world with a single word size anymore so carrying bit length in the name is critical, and so is keeping identifier names short. His trade off is exactly the one I would make.
- WalterBright 3y ago> carrying bit length in the name is critical I beg to disagree. In D: byte - 8 bits short - 16 bits int - 32 bits long - 64 bits absolutely nobody is confused about this.
- iforgotpassword 3y agoBecause we've used those names since forever, but that's archaic random crap really. Nothing apart from maybe "byte" makes sense here, the rest is completely arbitrary historic cruft. Could as well have called the rest timmy, britney and hulk.
- runiq 3y ago> Nothing apart from maybe "byte" makes sense here Lest we forget: https://web.archive.org/web/20170403130829/http://www.bobbemer.com/BYTE.HTM https://web.archive.org/web/20170403130829/http://www.bobbem...
- EPWN3D 3y agolol "no const". This is really groundbreaking stuff. Let's take a memory-unsafe language and make it even less safe.
- bandrami 3y agoRugby has much lower injuries than American football, it's often argued because rugby players don't use the helmets and padding and so are less willing to make catastrophically dangerous hits.
- waffletower 3y agoWould be great to see the author's treatment of memory allocation and lifecycle for complex data types.
- ok123456 3y agoSee his posts about arena allocations.
- kazinator 3y ago> #define sizeof(x) (size)sizeof(x) That breaks any macro that uses sizeof in its expansion, and subtly changes any code snippet you might bring into the code that uses sizeof, even if those macro are defined first. Speaking of which, if you define a macro for a C keyword before including any standard header, the behavior is undefined. It's an unparenthesized unary expression, which has a lower precedence than postfix. sizeof(x)[ptr] will turn into (size)sizeof(x)[ptr] which parses as (size) ( sizeof(x)[ptr] ).
- lsh123 3y agoI wrote and still maintain an open source C project for 20+ years. Once a year I get a new guy coming in and telling me I am doing it wrong: you should typedef all data types, you should stop using const, and so on. It stopped being funny after the first couple times.
- laserbeam 3y agoI'm getting an "I don't use const, and here's my view on it" vibe from the author much more than "you shouldn't use const". I'm really not getting any demand that you change your coding style, just someone reflecting on their work and explaining it to others. And... Whether I agree with their choices or not, I find that very cool and informative.
- SoftTalker 3y agoAnd, he also says "I’m not saying everyone should write C this way, and when I contribute code to a project I follow their local style."
- outsomnia 3y agoIt wasn't clear to me if he's talking about const as a variable declaration qualifier - I never used it - or const in pointer types, which is very useful.
- jstimpfle 3y agoFor me it's the other way around -- I use const for global variables because it makes a real difference, the data will be put in a .ro section. Pointer-to-const on the other hand (as in "const Foo *x") is a bit of a fluff and it spreads like cancer. I agree with the author that const is a waste of time. And it breaks in situations like showcased by strstr(). I use pointer-to-const in function parameter lists though (most of the time it does not actually break like in strstr()): as documentation, and to be compatible with code that zealously attaches const everywhere where there (currently) is no need to mutate. But overall my use of const is very very little and I generally do not waste my time (anymore) with it. I almost never have to use "const casts" so I suppose I can manage to keep it in check. In C++ it is a bit worse, when implementing interfaces, like const_iterator etc. That requires annotating constness much more religiously, and that can lead to quite a bit of cruft and repetition.
- travisgriggs 3y agoPretty much agree with most of this. My own personal evolved style is pretty similar. I'm suspect this kind of pragmatic style offends the theoretic and the academic. That can be intimidating. I'm glad at least one other person out there is like me.
- uwagar 3y agolooks like u tabstop at 4, i prefer 2. other than that our styles match :)
- Lockal 3y ago> typedef ptrdiff_t size; This reminds me of "#define max ..." in Windows.h. Not as bad, but if you autoreplace `sizeof(ptrdiff_t)` with `sizeof(size)`, good luck, because it will output size of type of size variable, if it exists in the scope.
- bandrami 3y agoYeah that's why the uppercase/lowercase hardline is a good one. Even Lisp, where free variable capture is a legitimate design pattern, has troubles with this.
- 0xedd 3y ago> code style > personal You missed the point of code style.
- lelanthran 3y agoIn general, the style the author has adopted is to introduce brevity where he can, and use wrappers over what would have been standard and idiomatic C code. In most situations, these conventions aren't good in a non-solo project, because they simply aren't as obvious to the programmer. He says so himself: > I don’t intend to use these names in isolation, such as in code snippets (outside of this article). If I did, examples would require the typedefs to give readers the complete context. That’s not worth extra explanation. Even in the most recent articles I’ve used ptrdiff_t instead of size. You require extra work to understand his basic types before reading even a short snippet, so he doesn't use it when he wants people to read short snippets. Introducing additional stuff the programmer must remember that does not add any safety is pointless busywork. A non-complete summary of his conventions: 1. typedef standard typenames to 3-char symbols, 2. remove qualifiers like const, 3. use macros to reduce the amount of typing the programmer does, 4. typedef all structs (and enums too, I assume) 5. A macro-ized string-typed with prefixed-length. > Starting with the fundamentals, I’ve been using short names for primitive types. The resulting clarity was more than I had expected, This isn't clear: `int8_t` is a lot clearer to a C programmer than `i8`, because a C programmer has already internalised the pattern of the stdint.h types. This is going to lead to subtle bugs as well: quick, according to his convention, what is the % specifier for `byte`? You can use %c, but that gives you an ascii character (which is not what we think of when we say 'byte'). If you use PRIu8 the compiler might give warnings because `char` might be signed. The best option is to just not use `byte` and use `uint8_t` instead (or, in his system, `u8`). Same with `b32` vs `i32` - it's a distinction without a difference and mixing these types won't give compiler warnings, while it is almost certainly an error on the part of the developer. Use `bool` if you don't like `_Bool`. In general I try to take advantage of whatever typing C provides; I don't try to subvert it because I want the compiler to warn me when my intention doesn't match the code I wrote. > No const. It serves no practical role in optimization, and I cannot recall an instance where it caught, or would have caught, a mistake. I disagree with dropping `const`. 1. It's useful as an indicator to the caller that the returned value must/must not be freed. It's a convention I use that makes it easy to visually spot memory leaks. Of the two functions below, it's clear to me which one needs the returned value `free()`ed and which one doesn't. const char *replace_substring (const char *src, const char *pat, const char *replacement); char *replace_substring (const char *src, const char *pat, const char *replacement); 2. It actually does catch a lot of problems, because the compiler warns me when I attempt to modify a value that some other code I wrote never intended to be modified. It's about intention, and when I know it is safe to modify the `const` value, then I have to explicitly cast away the const to compile my program. Anyone reading the program will know that the modification of the const-qualifed value is intentional, and therefore safe. > #define s8(s) (s8){(u8 *)s, lengthof(s)} This is interesting. I will try this out in my next project. I do think that there'll be quite a few compiler warnings for sign-mismatch though. This is the second "I wonder what the sign is" question for programmers reading his code - it means that his code has to compile with the flags that he compiles it with (I assume he's passing a flag to force chars to a particular sign). You can't simply compile his code in another project unless you copy his flags, and those flags may conflict with the new projects flags. I also wish that he'd showed a few examples of how having the length helps - what is presented in the post doesn't show any additional string safety over using nul-terminated strings. All those macros, including the one that creates the struct, could be written to operate on null-terminated strings. In essence, the length can be simply unused for everything! Where's the safety!? > It’s also led to a style of defining a zero-initialized return value at the top of the function, i.e. ok is false, and then use it for all return statements. On error, it can bail out with an immediate return. I use a similar pattern, but I use `goto cleanup` on all errors; you can't, as a general pattern, return early in a non-trivial C function without leaking resources. You can, as a general pattern, `goto cleanup` in every C function to clean up resources. I prefer the general pattern that I use everywhere rather than having to ensure that all resources acquired up to that particular return statement are released. > rather than include windows.h, write the prototypes out by hand using custom types. I think this is a very bad idea: you can't depend on the headers not changing after a compiler or library update. Sure, maybe in practice, all the Windows types and declarations don't change all that much, but I wouldn't want to be the developer trying to hunt down a bug because the interface to some function has changed and the compiler isn't giving me errors. All in all, I dunno if I would look forward to working on a team with these conventions - the code is harder to read, doesn't work in isolation, needs custom flags, and introduces a string type without introducing any string safety with it.
- zeroCalories 3y agoI think C really needs an update to the standard library that includes these shorter types. Seems like a fine list though. Some of my own style changes this year: I try really hard to write functional code. Mainly try to keep functions pure, and write declarative code. I find that this makes the code easier to write(not necessarily read), and I'm less scared of bugs. I also avoid malloc unless I absolutely need it. You can usually preallocate space on the stack or use a fixed length buffer, which pretty much avoids all fears of memory leaks or use after free type bugs. You will sometimes waste memory by allocating more than you need, but it's a lot more predictable.
- quelsolaar 3y agoYou can declare flexible array member strings with literals like this: #define DECLARE_STRING(variable_name, string) struct{size_t allocated; size_t used; char string[sizeof(c_string)];} variable_name ## internal = {.allocated = sizeof(c_string) - 1, .used = sizeof(c_string) - 1, .string = c_string}; MyString *variable_name = &variable_name ## internal Its a lot of C99 magic, so it may not be what you want but it is possible.
- jll29 3y agoThe notion of "personal style" is problematic, even for hobby projects, because (good) programming is ultimately a social activity. Even Linux started as a personal project, but because of its quality and the need it met it quickly spread. So please write your code in such a way that experienced other C programmers can read it easily. In isolation, I like some of his ideas, but some issues with C remain, and he is perhaps just to comfortable with C to jump ship and embrace Rust, which has many things he likes and more (e.g. no buffer overflows by design).
- pif 3y ago> No const. I stopped reading there. I wish this guy a happy coding (and non-coding) life, but I hope we never work together.
- cryo 3y agoI'd love to have him in a team. He truly cares exactly how code works and analyzes/fuzzes the hell out of everything. I don't agree with all stylistic choices in his code, but the level of experience and skills are far above most C developers.
- juped 3y ago"Skills" are subjective, but significant "experience" would involve traumatic foot-shooting turning him off most of the things advocated in this post.
- da39a3ee 3y ago> #define countof(a) (sizeof(a) / sizeof(*(a))) > #define lengthof(s) (countof(s) - 1) It makes no sense to use the word "length" to mean one less than the number of items. You could call it maxindexof perhaps. There may be good arguments for zero based indexing, but we have to also accept that there are downsides. One is that your code has to feature an artificial quantity obtained by subtracting one from a meaningful quantity.
- abareplace 3y agoThis is length of a null-terminated string, e.g. lengthof("abc").
- da39a3ee 3y agoOK, thanks I don't use C much and definitely forgot about that. But my point still stands doesn't it, in that the author is using these preprocessor macros for general arrays, which don't have a special terminator sentinel.
- fefe23 3y agoWhy would anyone care what the favorite whatever style of some dude on the Internet is? I like petunias! Now what? How does that help anyone?
- awestroke 3y agoI'll probably copy everything in the post for my next C project. Super nice stuff.
- fredrb 3y agoSharing preferences and opinions on code ergonomics certainly has value for me, and I bet it has to other people too. This is, after all, a developer's forum. I'm certain your opinion on petunias and your possible distaste for orchids will be welcomed in a flower-news type orange site. :-)
- forgotpwd16 3y agoYour preference doesn't help anyone. That's true. But coding styles may improve its reader's programming.
- lionkor 3y agoIsn't defining byte = char a bit wrong? char may be signed or unsigned, and may be more or less than 1 byte, right? So why that?
- tmtvl 3y ago> char may be signed or unsigned, Correct, the standard does not specify whether char is signed or unsigned, so it's implementation-specific. > and may be more or less than 1 byte, right? Wrong, char is specified as a single byte character, so the following will always be true: sizeof(char) == 1;
- Luker88 3y agoMore than a few of these look like what you will find in Rust by default Nice to see some evolutive convergence in C programmers, too
- nspattak 3y agoi love this blog, i hold Chris Wellons to a very high estime BUT i utterly disapprove the usage of macros so much, especially to wrap cstd types, functions, etc.
- jstimpfle 3y agoI'm currently on a C++ (mostly C with C++ compiler) trip. It does make some things easier and some things harder. It makes it easier to work with C++ developers :-). I sometimes use the more involved C++ features but often regret it after because of complications. But one thing that makes it worth it is the removal of the struct tag space. I have a strong dislike for the struct tag boilerplate in C, but the alternative -- typedef boilerplate -- in C is unbearable to the point that I have a macro to define structs in C that does this automatically. #define STRUCT(name) typedef struct name name; struct name STRUCT(Foo) { int x; int y; }; But macros often come with disadvantages. In this case it's that many IDEs have trouble finding the struct definitions from a usage site.
- neilv 3y ago> While I still prefer ALL_CAPS for constants, I’ve adopted lowercase for function-like macros because it’s nicer to read. "ALL_CAPS" in C was not for constants, but for preprocessor macros. It's shouting in all-caps, because it means "Look out! There's a cpp macro expansion here!" Related, please stop using "ALL_CAPS" for constants in other languages. Not only does shouting constants as the most prominent syntax in the code make no sense, but there are much better uses for shouting in a programming language. (For an example of a good use of "ALL_CAPS": if your language ever acquires Scheme-like template-based hygienic macro transformers, "ALL_CAPS" (or "ALL-CAPS") is excellent for making template pattern variables stand out within the otherwise literal code blocks.)
- jraph 3y agoInteresting take, but good luck with this fight against all caps (snake case) constants, at this point it's almost a consensus, a shared culture element, a deeply ingrained habit that the vast majority of developers have and recognize. Changing this would be a huge undertake I'd be afraid of engaging, if I cared that much.
- avgcorrection 3y agoThanks for bringing this up. There is no reason for modern languages that have proper constants (not preprocessor macros which happen to sometimes be used for them) to use this tedious style. Constants are so innocent and useful. Why indirectly discourage their use by making their usage an eye-bleed?
- barbs 3y agoWhat alternative would you suggest?
- avgcorrection 3y agoWriting in a normal way in snake case or camel case or whatever the convention is.
- yakubin 3y ago
- bArray 3y ago> typedef float f32; > typedef double f64; Assuming float is 32 bits and double is 64 bits sounds like a foot-gun. OpenCV defines a float16_t [0], CUDA implements half-precision floats [1], micro-controllers implement whatever they want. C++23 introduces fixed width floating-point types [2], but not aware of any way to enforce this in C. What I would suggest it to have a macro to check data is not lost at compile time. Generally I agree with others, it might be better to leave some of these things as default for readability, even if it is not concise. [0] https://docs.opencv.org/4.x/df/dc9/classcv_1_1float16__t.html https://docs.opencv.org/4.x/df/dc9/classcv_1_1float16__t.htm... [1] https://docs.nvidia.com/cuda/cuda-math-api/group__CUDA__MATH____HALF__MISC.html https://docs.nvidia.com/cuda/cuda-math-api/group__CUDA__MATH... [2] https://en.cppreference.com/w/cpp/types/floating-point https://en.cppreference.com/w/cpp/types/floating-point
- CodeArtisan 3y agogcc has _Float<size> types typedef _Float32 f32; typedef _Float64 f64; https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html
- sigsev_251 3y agoIt's not just gcc, they are in the C23 standard along with the _Decimal<size> types.
- thesnide 3y agocomments here are a canonical example of bikeshedding...
- jeffrallen 3y agoI came back to C after a good long time in Go, and I found that my C style had picked up some of these same good ideas, which I attributed to Go. In particular I also swore off NUL terminated strings, and started using structure returns to send back multiple values.
- moodguy 3y agoFor my hobby projects i do the following. I adopted the style of writing all macros in lower case with the prefix "macro_", so i can grep through all macros. So macro_ becomes like a keyword. Same with enum. I use almost the same naming scheme for i32,f32, etc., but i typedef for example size_t const to usz, and size_t to usz_ (or use macro mut(x) x ## _) . So all shorter type names are const by default. I use a single header of 35 sloc to do that. I see that this way of coding can be confusing for other people, so i avoid it when writing code that other people have to work with. But for personal code its really enjoyable for me.
- loondri 3y agoI get that everyone has their own coding style, but ditching established conventions in C for personal aesthetic seems a bit much. Like, using u8 or i32 instead of the standard uint8_t or int32_t might save a few keystrokes, but it could confuse anyone else looking at the code. And the custom string type over null-terminated strings? C's built around those, and deviating from that just feels like making life harder for anyone else who might need to work with your code. And manually writing out Win32 API prototypes instead of including windows.h might shave off some compile time, but it's like ignoring a well-maintained highway to trek through the woods. Just seems like a lot of these changes are about personal preference rather than sticking to what makes C code easy for everyone to work with.
- michaelcampbell 3y ago> Like, using u8 or i32 instead of the standard uint8_t or int32_t might save a few keystrokes, but it could confuse anyone else looking at the code. This seems a theoretical possibility _at best_, and a fairly strawman-y one at that. I doubt any competent C programmer would get "confused". Irritated at a different style, maybe. Too, everything is hard to read before you learn to read it. -- Rich Hickey
- JonChesterfield 3y agou16 etc show up a lot and are unlikely to confuse programmers. Where it does go to pieces is when two different programs both define u16, use them in header files, and then a third program tries to include both those header files at the same time. The big advantage of <stdint.h> is avoiding that failure mode. The namespaced library type equivalent is something like libname_u32, at which point it's tempting to write uint32_t instead of the libname:: or libname_ prefix.
- SkeuomorphicBee 3y ago> Like, using u8 or i32 instead of the standard uint8_t or int32_t might save a few keystrokes [...] It is not about saving keystrokes, it is about reducing sensory load when reading it. Sorry, I know it may sound like I'm splitting hairs, but every single time when the argument of verbosity vs conciseness in programing languages comes around, this "keystrokes" argument is thrown and it is extremely flawed. The core belief that conciseness is only better for faster typing but that verbosity is somehow always better than conciseness for reading is just plain wrong and we should stop using it. And yes, verbosity has some advantages for reading comprehension, but so does conciseness, no side is a clear winner, it is all about the different compromises.
- al_be_back 3y ago>> typedef all structures. I used to shy away from it it seems this may be more an issue with being shy, than a coding issue :P in my view, language is a Style, so coding in C is c-style coding. when i started coding in python (small projects initially), my code cried-out Java Java (typing, packaging, naming, oop), and took nearly as long to write - I laughed my head of when I realized, just because I could doesn't mean I should.
- JonChesterfield 3y ago> typedef char byte; That one is dubious. Char has magic aliasing properties that uint8_t might not have (iirc that was contentious in a GCC bug report) and it will be signed on some platforms and unsigned on others, which changes implicit integer conversions. Missing from this is to embrace attribute((overloadable)) and attribute((cleanup)). Overloadable is the sane, useful alternative to the thing standardised as _Generic. The C _Generic will let you define an overload set, with some weirdness around type conversions, provided you write the entire set out as a single _Generic expression, probably wrapped in a macro. If you want to dispatch on more than one argument, you nest _Generic expressions. If you want to declare different functions in different headers - maybe you want 'size(T)' defined on various types in the codebase - you can't. If you don't like the idea of thousands of lines of distracting nonsense in the preprocessed output, tough. Or - use overloadable, get open overload sets, minimal compile time cost, obvious intermediate IR, everything works. Prior art is all of C++, so talking decades of the tooling learning to deal with it. Cleanup is either a replacement for raii, or a means to have debug builds yell at you when you miss a free. It looks like that got warped into a thing called 'defer' with different behaviour that didn't make it through the committee last time. Other than that, ad hoc code generators work really well with C. Especially if you're willing to use some compiler extensions. Code generators + overloadable will give a fair approximation to templated data structures without going deep into the insanity of the preprocessor. If the overloadable functions are static inline forwarding things in a header they don't even mess up symbol names; you just get a straightforward translation to vector_float_size or whatever. Personally I've given up on ISO C. I'd quite like to code in a dialect of C99 with a few of the GNU extensions and the equivalent of `fno-strict-aliasing`, but C with the pointer provenance modelling and an accretion of C++ features has no personal value. Currently still using clang with flags to make it behave like that but I'm conscious that's on borrowed time - the application performance friendly aliasing rules are the default and gaining popularity, and relying on opt-out flags is a means of opting into compiler bugs. Semi-actively seeking something that will let me write assembly without the hassle of manual register allocation and calling conventions. Old style C with some of the warts bashed off would be good for that.
- stephc_int13 3y agoOne nice thing about short types (i32 f32 u16 etc.) is that they can easily be extended for vector types such as f32x4 or u8x16 etc. Consistency, conciseness and clarity (you don't have to guess much about those, once you've understood the naming scheme)
- Pathogen-David 3y agoI actually have the opposite feeling. I like the non-sized type names like float extended as float4 or float4x4. My brain wants to read f32x4 as a 32 by 4 matrix of floats. (That being said I'd definitely be interested in trying your convention in the context of something like Rust.)
- stephc_int13 3y agofloat4 is nice, but in video games, especially graphics, the vast majority of our floats are 32 bits but we also use 16 bits floats quite extensively, and in this case it is quite practical to follow the same convention. f32 -> f16 f32x4 -> f16x4 And when working on some heavily optimized SIMD code on the CPU side, I tend to use the default types even less.
- Pathogen-David 3y agoMy preference for float4 comes from HLSL so I'm aware. For vectors of 16 bit floats my preference would be half4 etc. (Although I do concede that in HLSL land that doesn't actually do what you want on older language versions.) IMO including the number of bits in all primitive types is usually an overcorrection from trauma caused by C/C++'s historic loose definitions of primitive type sizes. However I don't write much CPU SIMD code and can definitely see how you'd develop your preference from that context.
- uxp8u61q 3y agoHonest question: why C? I've been writing C++ firmware for SoCs, supposedly an area where C is supposed to be king, and C++ was just as good, except it came with all the batteries included. So, why C?
- habibur 3y agoIn my case : - I prefer functions over classes. - no mangling of exported names, the binary is re-usable as API. - in the long term, the C source code is more re-usable in other projects than C++ ones. and more like this.
- kovacs_x 3y agosure... and what about time spent debugging yet another unallocated/untimely freed/off by 1 pointer mistake? ps. you know you can write C++ code that is functional and use free functions primarily instead of putting everything in classes?
- habibur 3y agoyou can run valgrind and it will just point to you "you freed the memory on this line and then tried to reused it 10 lines bellow here, fix it." -- every time. And once you fix it, you have built a light weight library that you can use from any other language. Also these pointer manipulation is what gives C its power.
- uxp8u61q 3y ago> I prefer functions over classes. Then use functions? > no mangling of exported names, the binary is re-usable as API. Not once in my life have I seen someone use a binary as an API. > in the long term, the C source code is more re-usable in other projects than C++ ones. I don't even know what you mean by that.
- lelanthran 3y ago> I don't even know what you mean by that. He means that if you write your library in C it is callable from Python, Java, C++, Ruby, PHP, Python, Perl and more.
- sylware 3y agoI would not use typedefs as this should not be in the C syntax (like enum,switch, and much more). The primitive type names should be native, but to "fix" C I prefer using the C preprocessor (I use it for namespace/name mangling too). This is not perfect, but should be already way more than enough. With proper preprocessor usage (without going amok), one can write one compilation unit software roughly easily.
- varispeed 3y agoBeen there. Writing u8 instead of uint8_t may seem like a time saver, but in reality it makes it more difficult to read and reason about. If you pack too much information, you are taxing your brain more, you are slower to analyse the code and you make it easier to make mistakes. Now I much prefer code that is as verbose as possible.
- vitiral 3y agoGreat article. One thing for me is that I think we named our variables wrong. We should be specifying the number of bytes, not bits. I use U1, U2, U4, U8, I1, I2, etc Also S for "slot" aka unsigned pointer sized integer (usize_t) Another big point is formatting code to line-up instead of with an autoformatter. When you are doing something which is almost the same but slightly different it helps readability considerably. It is also a sign of a well-loved codebase, since I've never seen an autoformatter that can do it. Maybe we could make formatters at least auto _detect_ that code is already aligned and to just leave that code alone. Some kind of "love heuristic"
- ggliv 3y agoCould you elaborate on the reasoning behind using byte length instead of bit length? Most of the time when I use fixed-width int types I’m trying to create guarantees for bitwise operators. From my perspective I feel like it therefore makes the most sense to name types on a per-bit level.
- vitiral 3y agoWe almost always talk in bytes. When trying to reason about alignment it's bytes, when reading a serial IO from a file it's bytes. I hardly ever think in bits and when I do, I think in hex not decimal. I also like that it makes all the type names the same width (notably U1/U2 vs u8/u16).
- dathinab 3y agosize_t not mapping to size but size being defined is a footgun I would have used isize instead, I think.
- beeforpork 3y agoHopefully I never need to review code written with these definitions. It's an awful idea to do this. The first section shows why some languages have no 'typedef': introducing another layer of aliases is just not a good idea. It's confusing, it changes the appearance to basically a new language. Just use the standard names, instead of redefining your language, like everyone else. This style is almost as bad as '#define begin {'. Many of the other defs are obfuscations or language changes -- this coerces C to something else. I'd not like to read code written with this, as it heavily violates the principle of least astonishment (POLA). (As a side note, I don't understand the #define for sizeof. The operator sizeof returns size_t -- it's size_t's definition, so what is this for?)
- Pathogen-David 3y ago> As a side note, I don't understand the #define for sizeof. Their `size` type is signed. It's `ptrdiff_t`, not `size_t`.
- beeforpork 3y agoAh, thanks! So code will look normal, but be subtly different. This is even worse than I thought!
- bullen 3y agoThis is my personal style: http://edit.rupy.se/?host=move.rupy.se&path=/file/game.cpp&space http://edit.rupy.se/?host=move.rupy.se&path=/file/game.cpp&s... It's messy and in many ways ugly but it compiles and runs for an eternity.
- monomere 3y ago> using namespace std and then you put the code in a header and everything explodes
- deleted 3y ago[deleted]
- gilcot 3y agoWhen I started reading the first section, about his own types, I couldn't help thinking: oh my, sounds like "Hungarian notation"[1] :) I think those definitions go to a header file. But how different will it be if he use existing types with an abbreviation system? And is this feature available with some IDE? [1] https://en.wikipedia.org/wiki/Hungarian_notation https://en.wikipedia.org/wiki/Hungarian_notation
- adriangrigore 3y agoAgree on no _t suffixes!
- Darbyannskinner 3y ago[dead]
- CatrionaBath14 3y ago[dead]
- angra_mainyu 3y agoFascinating how he's incorporated lessons from Go and Rust. The short primitives are definitely a boon for readability and using the s8 type over null-terminated strings is something that should be built in.
- omniscient_oce 3y agoCan anyone explain the use of ptrdiff_t instead of size_t (he has typedef'd "size" for ptrdiff_t) The macros are wrapping _Alignof and sizeof both which can't return non-zero numbers I thought.
- bergkvist 3y agoptrdiff_t is a signed integer type - which means you can subtract these numbers without worrying about underflow
- saw-lau 3y agoI love some of the ideas here! Particularly the neat string 'class' and hand-defining Windows API functions instead of dragging in Windows.h. There's clearly life in the old dog yet!