Live data from Hacker News

My personal C coding style as of late 2023

nullprogram.com

21–30 of 466 posts

Re: My personal C coding style as of late 2023

#21

> 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 an…

It depends entirely on the architectures | CPUs, that said the obvious case from past experience is numeric processsing jobs where (say) you flow data into "per cycle" structs that lead with some conditionals and fill out with (say) 512 | 1024 | 2048 sample points for that cycle (32 or 64 bit ints or floats) .. the 'meat' of the per cycle job.

My specific bug bear here was a junior who insisted "saving space" by packing the structs and using a single 8 bit byte for the conditionals.

Their 'improved' code ground throughput on intel chips by a factor of 10 or so and generated BUS ERRORs on SPARC RISC architectures.

By packing the header of the structs they misaligned the array of data values such that the intel chips were silently fetching two 32 bit words (say) to get half a word from each to splice together to form a 32 bit data value (that was passed straddling a word boundary) to pipe into the ALU and then do something similar to repack on the other end - SPARC's quite sensibly were throwing a fit at non aligned data.

Point being - sometimes it makes sense to fit data to the architecture and not pack data to "save" space (this is all for throughput piped calculations not long term file storage in any case)

Re: My personal C coding style as of late 2023

#22
post #15

> #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.

> 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?

Re: My personal C coding style as of late 2023

#23
post #7

A 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 aroun…

i never thought about that, saving bytes even in symbols

Re: My personal C coding style as of late 2023

#24
post #16

> 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 an…

Computer architecture is optimized for 32+ bit aligned access to most things. The gain is (usually, but not always!) performance.

I'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.

Re: My personal C coding style as of late 2023

#25

> 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 an…

Most of the time an easy optimization is to pad fields of your struct to a 32 bit boundary. Almost any compiler will do this for you (look up "struct alignment / padding"). If the compiler is going to do this anyway, might as well use the memory yourself instead of letting it be empty space. If it doesn't happen, you leave performance on the table, so doing this raises the chance that your struct/fields will be aligned.

Nuance is that each field should be at an address divisible by the fields size or wordline size, not some magic 32 constant. The entire struct should also be padded to a multiple of the largest fields size. In practice this usually means 32 bit alignment.

Ref http://www.catb.org/esr/structure-packing/

Re: My personal C coding style as of late 2023

#27
post #21

> 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 an…

It depends entirely on the architectures | CPUs, that said the obvious case from past experience is numeric processsing jobs where (say) you flow data into "per cycle" structs that lead with some conditionals and fill out with (say) 512 | 1024 | 2048 sample points for that cycle (32 or 64 bit ints or floats) .. the 'meat' of the per cycle job. My specific bug bear here was a junior who insisted "saving space" by pack…

This is the use case for `uint_fast8_t` (part of the C99 standard); it should use whatever width of unsigned integer is enough to store a byte, but fastest for the platform. You always know that the type can be serialized as 8 bits, but it might be larger in memory. So long as you don't assume too much about your struct sizes across platforms, it should be a good choice for this. Although, if alignment is an issue, it might be a bit more complicated depending on platform.

Re: My personal C coding style as of late 2023

#28
post #7

A 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 aroun…

i never thought about that, saving bytes even in symbols

I think he is suggesting the opposite - use more verbose names for clarity.

Re: My personal C coding style as of late 2023

#29
> 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.

Post reply on HN