Live data from Hacker News

Summary of C/C++ integer rules

nayuki.io

61–70 of 98 posts

Re: Summary of C/C++ integer rules

#61
post #14

Earlier quoted context omitted.

It depends on what you are doing. For some kinds of programs, C/C++ are going to be much faster than most "modern" languages.

Most, but not all. Languages like Rust and Zig show that you can have the performance without the landmines.

Also, theoretical performance is overrated. Almost all the things that lends themselves to speed make code brittle and incapable of future modification.

Once you’ve got your C code doing safety checks with data types that won’t break under the littlest change, the code becomes much slower than code golf would suggest. A common example is passing void pointers everywhere. You either check every call every time (aka dynamic typing) or rush everything on the idea that the programmer understands the system completely and never forgets or messes up. Better types give you all the speed AND all the safety here.

Re: Summary of C/C++ integer rules

#62
post #43
post #18

Earlier quoted context omitted.

I didn't mean C is not fast or not faster than other languages. It's still the fastest one I believe. What I meant is undefined behaviors allow compilers to optimize in a way that would not be possible otherwise. So, it might be a deliberate decision back then, to leverage performance. I don't know, just an idea.

It used to be "folk knowledge" that only Fortran and hand-crafted ASM were faster. Not sure if that's still (or ever was) true.

I guess it was maybe true one time.

http://www.catb.org/jargon/html/story-of-mel.html

Re: Summary of C/C++ integer rules

#63

Earlier quoted context omitted.

> No need to enforce your semantics through type Maybe I'm spoiled by other languages with more powerful type systems, but this is exactly what I want my types to do! Isn't this why we have type traits and concepts and whatnot in C++ now? If not for semantics, why have types at all, the compiler could figure out what amount of bytes it needs to store my data in, after all. I use types for two things: to map semantics…

Would you really write a function find_prime_factors() that takes an input of type "integer" and an output of type "prime", that you have previously defined? Then if you want to sum or multiply such primes you have to cast them back to integers. Maybe it makes sense for you, but for me this is the textbook example of useless over-engineering. The same ugliness occurs when using unsigned types to store values that hap…

> Would you really write a function find_prime_factors() that takes an input of type "integer" and an output of type "prime", that you have previously defined?

If the language allows me to and its an important semantic part of my program, then yes. The same way as I would create types for units that need conversion.

Unless I'm writing low level performance sensitive code, yes, I want to encode as much of my semantics as I can, so that I can catch mistakes and mismatches at compile time, make sure units get properly converted and whatnot.

> What's so special about the lower bound of the possible set of values?

Nothing, I would encode a range if I can. But many things don't have a knowable upper-bound but do have a lower bound at zero: you can't have a negative size (for most definitions of size), usually when you have a count of things you don't have negatives, you know that a dynamically sized array can never have an element index less than 0, but you may not know the upper bound.

Also, the language has limitations, so I have to work within them. I don't understand your objection for using what is available to make sure software is correct. Also, remember that many of the security bugs we've seen in recent years came about because of C not being great at enforcing constraints. Are you really suggesting not to even try?

> And this is a can of worms that I prefer not to open...

And yet many languages do and even C++20 is introducing ranges which kind of sort of fall into this space.

Re: Summary of C/C++ integer rules

#64

In the myths section: > char is always 8 bits wide. int is always 32 bits wide > Signed overflow is guaranteed to be wrap around. (e.g. INT_MAX + 1 == INT_MIN.) Are there any current, relevant hardware architectures where this is not true (e.g. bytes are not 8 bits, and integers are not 2's complement)? E.g. what's the point of "portability" if there is no physical hardware around anymore where those restrictions wou…

DSP's have often uncommon sizes. tms320c5502 for example has following sizes: char-- 16 bits short --16 bits int --16 bits long-- 32 bits long long -- 40 bits float-- 32 bits double -- 64 bits

> 40 bits float-- 32 bits double

Isn't double required to have more precision than float?

Re: Summary of C/C++ integer rules

#65

Earlier quoted context omitted.

> Unsigned types are quite useful when doing bit twiddling because they don't overflow or have a bit taken up by the sign. That's essentially their only application. The rest are stupid single-bit memory-size optimizations. As Jens Gustedt noted, it's one of the (many) misnomers in the C language. It should be better called "modulo" instead of "unsigned". Other such misnomers that I recall: unsigned -> modulo char ->…

> That's essentially their only application. What about when it doesn't make semantic sense to have negative values? Eg for counting things, indexing into a vector, size of things. If negative doesn't make sense, I use unsigned types. Its not about the memory-size in that case.

If negative doesn't make sense then you are saving one bit using this method, but introducing a ton of fun footguns involving things like conversions. Further, the compiler cannot assume no overflowing and must now do extra work to handle those cases in conforming fashion, even if your value width doesn't match the CPU width. This can make your code slower!

Re: Summary of C/C++ integer rules

#66

Earlier quoted context omitted.

Positive values are a particular case of signed values, you can still use signed ints to store positive values. No need to enforce your semantics through type, and especially not when the values of the type are trivially particular cases of the values of another type. For example, when you write a function in C that computes prime factors of an int, do you need a type for prime numbers? No, you just use int. The same…

> No need to enforce your semantics through type Maybe I'm spoiled by other languages with more powerful type systems, but this is exactly what I want my types to do! Isn't this why we have type traits and concepts and whatnot in C++ now? If not for semantics, why have types at all, the compiler could figure out what amount of bytes it needs to store my data in, after all. I use types for two things: to map semantics…

But "unsigned" doesn't actually enforce the semantics you want. Missing an overflow check means your value will never be negative, but it is almost certainly still a bug. And because unsigned overflow is defined, the compiler isn't allowed to prevent you from doing it!

This is just enough type semantics to injure oneself.

Re: Summary of C/C++ integer rules

#67

In the myths section: > char is always 8 bits wide. int is always 32 bits wide > Signed overflow is guaranteed to be wrap around. (e.g. INT_MAX + 1 == INT_MIN.) Are there any current, relevant hardware architectures where this is not true (e.g. bytes are not 8 bits, and integers are not 2's complement)? E.g. what's the point of "portability" if there is no physical hardware around anymore where those restrictions wou…

There are loads of DSPs, MCUs, and other non-PC junk where CHAR_BIT is not 8. For example of the SHARC, CHAR_BIT is 32, absolutely every type is 32 bits wide.

Re: Summary of C/C++ integer rules

#68

In the myths section: > char is always 8 bits wide. int is always 32 bits wide > Signed overflow is guaranteed to be wrap around. (e.g. INT_MAX + 1 == INT_MIN.) Are there any current, relevant hardware architectures where this is not true (e.g. bytes are not 8 bits, and integers are not 2's complement)? E.g. what's the point of "portability" if there is no physical hardware around anymore where those restrictions wou…

C code doesn't just run on the architecture you compile for. It first "runs" on a C virtual machine simulated by the optimizer. This low-level virtual machine (you may call it LLVM) usually implements signed overflow by deleting the code that caused it.

Re: Summary of C/C++ integer rules

#69
post #64

Earlier quoted context omitted.

DSP's have often uncommon sizes. tms320c5502 for example has following sizes: char-- 16 bits short --16 bits int --16 bits long-- 32 bits long long -- 40 bits float-- 32 bits double -- 64 bits

> 40 bits float-- 32 bits double Isn't double required to have more precision than float?

I think their formatting got swallowed by HN:

char-- 16 bits

short --16 bits

int --16 bits

long-- 32 bits

long long -- 40 bits

float-- 32 bits

double -- 64 bits

Re: Summary of C/C++ integer rules

#70

Earlier quoted context omitted.

This is the trap with 'undefined behaviour': it has nothing to do with portability, but it is a language level definition. I.e., if the C std says it's 'undefined', it is not to be avoided for portability reasons (hardware, assembler), but it must not be used, end of story. The portability stuff is called 'implementation defined' in C, not 'undefined behaviour'. The problem is that the compiler can (and will!) exploi…

"Undefined behavior" really means that the standard doesn't define what should happen and that the compiler is therefore free to do whatever it pleases, under the assumption that such code will never occur. Reminds me of the examples where the code gets compiled in a way where a branch that returns from the function is unintuitively always taken because the compiler was able to detect that there is undefined behavior…

> So yeah, undefined behavior isn't "implementation defined" nor "unportable" but rather "illegal not allowed wrong code".

There are edge-cases even there. Calling a function generated by a JIT compiler is undefined behaviour, but there's a gentleman's agreement that the compiler won't screw it up for you.

Almost all C/C++ compilers promise that floating-point division-by-zero results in NaN (the IEEE 754 behaviour), but according to the C/C++ standards themselves, it's undefined behaviour.

You're right though that in general, one should not be complacent about UB.

Post reply on HN