Live data from Hacker News

22nd Century C

procedural.github.io

51–60 of 106 posts

Re: 22nd Century C

#51
post #9

I've spent two decades writing C and C++, but the last 8-9 years in really high-level languages (Ruby, Javascript, Python). From either end of the spectrum, I've never felt the need for such emphasis on fixed-sized numeric types. I've commonly needed access to fixed size numerics, like when sending texture formats to the GPU, defining struct layout in file formats and network protocols, but I have never once thought:…

When browsing code posted or advertised on various forums, I noticed a dramatic increase in the use of uint8_t and such. In places where they really were not useful, and a basic standard type would suffice, and be more portable, and be as fast or faster. I have seen intN_t used as booleans! I have seen uintN_t types used and signed values put in them! (which meant the author didn't understand yet the basis of types but was somehow taught it was a good practice to use exact-width types).

I also suspect the influence of languages with fanboys, as Rust. I had a 'fight' with these, they really didn't grasp the concept of having a type defined as the natural type of the platform and as having at least (and possibly at most) N bits. I gave up. As long as they have fun with their thing and do not try to spread "the good word" and influence other languages, that would be fine.

Let's hope we won't see the influence of languages whose designers had a gripe against unsigned integers...

Re: 22nd Century C

#52
post #12
post #9

I've spent two decades writing C and C++, but the last 8-9 years in really high-level languages (Ruby, Javascript, Python). From either end of the spectrum, I've never felt the need for such emphasis on fixed-sized numeric types. I've commonly needed access to fixed size numerics, like when sending texture formats to the GPU, defining struct layout in file formats and network protocols, but I have never once thought:…

Looks like you haven't worked on embedded systems. We need to be very careful with sizes of ints here. Not just because we run the risk of overflows, but also because when we create code that may have to be ported from one architecture to another, we want to minimize re-work. > Notice how smooth the 32-bit to 64-bit transition went? There are many reasons for that. For most PC work, a 32-bit int is more than large en…

You're not wrong, but it seems you're not abstracting enough (and depends on your embedded system)

However the smaller they are the less complex the system is. And running higher level languages is not as taboo today

Re: 22nd Century C

#53
post #41
post #21

Earlier quoted context omitted.

C's normal integer types work the closer to the way you want. When you say you're returning an int, you're not really specifying the exact size at all. For those who don't know, C's char, short, int and long data types aren't really "fixed size". The standard defines their order: sizeof(char) Now you may be thinking "sure the standard says they could be different, but they aren't really, a char is 8bit, a short is 16…

Is there any advantage in practice to using these loosely defined types (char, short, int, long) vs their precise counterparts?

One example is that if you don't need a specific size, using the loosely defined types may be faster. For example, if you specify int32_t, but your program compiles for a 16-bit platform, then instructions operating on this variable may need to run twice, since the registers can only fit 16 bits of data.

And the other way around: If you specify int8_t, but your program runs on a 64-bit platform which only can address memory in 64 bit chunks, then you might waste 32-bits of memory for this variable, and addressing it can be slower since the program has to find the 8 interesting bits in a 64-bit register, and discard the rest of the bits so they don't affect calculations.

The only reason to use specific sizes IMO is if you work with loads of data and need to pack it efficiently, or work with bit-specified protocols like TCP packets or files. If you need _at least_ 16 bit of data for example, then you can use the int_least_16_t, which may be 64-bit or 16-bit depending on the architecture.

Re: 22nd Century C

#54
post #33

Earlier quoted context omitted.

> How do you port it to a machine without a 16-bit type? The problem is made more severe when you use "int" and let the compiler decide the size of the variable. I guess I don't know what you're advocating. > The stuff about communicating with the outside world is quite different. I meant communicating as a throwaway example. It's a valid example, but really, everything gets affected. For example, a CAN identifier is…

If you use "int", you know you have at least 16-bits to play with. Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks? 1. If you are dumping it out to file or similar (i.e. assuming the layout in memory) then your code is no longer portable between different endian machines. 2. If you are relying on overflow behaviour, then your code is broken already (this doesn't…

> Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks?

Memory. Defaulting to 16-bits when an 8-bit variable will do can be incredibly wasteful on an 8-bit µC. Keep in mind that we are not just talking about one variable in isolation. We are talking about all the integers we pass between functions. We are talking about code space, data space, and stack space. There are compilers that can optimize their arithmetic operations to 8-bit registers when they can be sure that that's all their operands need.

> Would you suggest a uint29_t?

If you are aware of a machine that provides that, yes! Otherwise, I'd suggest uint32_t. Yes, "long" is guranteed to be 32-bits wide, but it can also be 64-bits wide. I would not recommend defaulting to "long", as that could be wasteful. Here's an interesting discussion about the meaning of "long" and "long long" for their compiler: https://www.dsprelated.com/showthread/comp.dsp/42108-1.php. I see this discussion as a failure of the C standard.

I would much rather the compiler provided int64_t or int40_t or whatever else they can, that is not inefficient.

Re: 22nd Century C

#55
post #5

I don't get it. Could someone summarize/explain this.

I'm only guessing, but it looks like a C header file with a set of macros that produces neat-looking C code. Not sure what's the deal with the 22nd century thing.

Probably an answer to the "21st century C", an O'Reilly book about how to write modern C.

Re: 22nd Century C

#56
I wonder if this is in any way inspired by this - https://github.com/google/honggfuzz/blob/master/common.h ?

I use there defer for both gcc/clang, countof(arr) -> ARRAYSIZE(array)

In any case, yeah, going through gcc/clang internals, and through C11 standards gives people some ways to speed-up and clena-up their implementations a bit.

Re: 22nd Century C

#57
post #16

Very cool! I wasn't aware of the cleanup attribute, can't wait to try it out: The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the functio…

FYI - here's defer implementation for both gcc/clang - http://pastebin.com/EXZuRAdT

Re: 22nd Century C

#58
post #9

I've spent two decades writing C and C++, but the last 8-9 years in really high-level languages (Ruby, Javascript, Python). From either end of the spectrum, I've never felt the need for such emphasis on fixed-sized numeric types. I've commonly needed access to fixed size numerics, like when sending texture formats to the GPU, defining struct layout in file formats and network protocols, but I have never once thought:…

For a university project I programmed for an Arduino Uno (ATmega328P). Debugging performance problems, we found out that a switch (implemented with a bunch of if/else ifs) with about 12 branches used ca. 200 clock cycles just checking the 12 conditions.

Turns out the ATmega328P has only 8bit registers and the ALU operates on 8bit values. We were using 32-bit datatypes in the conditions, which the ATmega328P loaded and compared byte per byte, each comparison (incl. jump etc) cost us something like 16 cycles.

So yeah, the size of the datatypes definitely mattered for us :)

edit: as an addition, we needed quite a while to figure out (and were quite surprised when we did) that ints are 16bit and longs 32bit on this platform/compiler. i guess this comes down to the general question of explicit vs implicit, and i usually do prefer the former.

Re: 22nd Century C

#59
post #54

Earlier quoted context omitted.

If you use "int", you know you have at least 16-bits to play with. Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks? 1. If you are dumping it out to file or similar (i.e. assuming the layout in memory) then your code is no longer portable between different endian machines. 2. If you are relying on overflow behaviour, then your code is broken already (this doesn't…

> Take some correct code that works with int16_t, and replace the "int16_t" with plain "int". What breaks? Memory. Defaulting to 16-bits when an 8-bit variable will do can be incredibly wasteful on an 8-bit µC. Keep in mind that we are not just talking about one variable in isolation. We are talking about all the integers we pass between functions. We are talking about code space, data space, and stack space. There a…

> Memory. Defaulting to 16-bits when an 8-bit variable will do can be incredibly wasteful on an 8-bit µC.

I hope it doesn't seem like I'm just moving the goalposts, but the obvious answer here is to use a type which is at least 8-bits wide if you only need 8-bits. Such a type exists, and is called "char" (with the appropriate signedness modifiers).

This whole discussion has been about writing portable code. Using a "char" here is going to work perfectly on your 8-bit uC. It is also going to work perfectly on your SHARC chip with 32-bit chars.

> If you are aware of a machine that provides that, yes! Otherwise, I'd suggest uint32_t.

I think we are talking about different things. I am talking about writing portable code. You are talking about writing code that only works on a particular machine (or the class of machines that have a 29-bit int).

I know there is a place for that code, and once you are writing code where you actually need to make use of knowledge about the machine, then I'm all for using types that make this clear. However, typically this code only lives at the edges of the system, and the actual "computation" can be written in portable, efficient, readable code without a great deal of trouble.

I'm completely aware that long can be 64-bits on some machines. If you care so much about the wasted memory, then uint_least32_t should make you happy - if you are happy with the C99 dependency (which can limit portability, though things are getting better), then I don't see how you can see this as being worse than the fixed width uint32_t.

Personally, I have found that while it sounds nice in theory, the domains I've been working in have meant that the memory doesn't make much difference (if it is just sitting on the stack or being passed between functions, then on 64-bit machines, there is typically no differences, as calling conventions tend to pad things out). It is only when you have an array of these in memory that it might start to matter, and here it tends not to matter a great deal - you are typically now optimizing an algorithm for an amd64 machine (read - it has plenty of memory) and the algorithm typically doesn't actually use a lot of it (since it needs to run on tiny micros too).

Anyway, I think we probably agree for code which isn't supposed to be portable. Potentially just that I tend to work more on the code that is supposed to be portable, and you work on the code at the edges?

Re: 22nd Century C

#60
post #30
post #22

Earlier quoted context omitted.

Yeah, I'm arguing that the situation you describe (accurately) is better than baked-in sizes all over the source code. Use the platform-native types (whatever size they may be) unless you have a reason not to, then use or an analogue. If CHAR_BIT is 13, let char be 13 bits: the platform probably chose that for a reason. When you have to pack it into a TCP header, do your strict fixed-width stuff there.

I completely agree with you when it comes to function return types. For data structure fields, especially structs used many, many times like in very large arrays, I'd say it's sometimes worth using fixed size types to get better control over memory use. Using a 64-bit int for a field a 16-bit integer can handle will use up 4x as much memory. And if you've got a ten or a hundred million structs of that type, then it r…

I strongly disagree with your sentiment.

If you wrote an application that's as efficient as possible without any wasted bits in the size_t type, it then only works on your machine.

If I wanted to run such an application on my supercomputer with 2TB of RAM (such machines exist), I would then have to recompile for a 41-bit size_t.

We use machine-neutral (but architecture-specific) size_t for these kinds of things explicitly to avoid recompiling on different machines that are instances of the same platform.

Said another way, binary distributions could not exist if everything was made efficient for the underlying hardware. It would stink to have to recompile the world after upgrading RAM.

I'd rather have a few bits of wasted space (which are typically lost anyway due to struct packing) than lose intra-platform comparability.

Post reply on HN