Live data from Hacker News

Summary of C/C++ integer rules

nayuki.io

51–60 of 98 posts

Re: Summary of C/C++ integer rules

#51

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…

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 later in the function and since undefined behavior isn't legal, it assumed that it therefore can never reach there, so the branch must always get taken and the actual condition check got optimised away (IIRC).

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

Re: Summary of C/C++ integer rules

#52

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.

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 thing for positive numbers, and for even numbers, and for odd numbers. You can and should do everything with signed integers, except bitfields, of course.

Re: Summary of C/C++ integer rules

#53

Earlier quoted context omitted.

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

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…

> Positive values are a particular case of signed values, you can still use signed ints to store positive values.

And yet Java's lack of unsigned integers is considered a major example of its (numerous) design errors.

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

Of course not, there's no need for any type at all, you can do everything with just the humble byte.

> The same thing for positive numbers

No?

> You can and should do everything with signed integers

You really should not. If a value should not have negative values, then making it so it can not have negative values is strictly better than the alternative. Making invalid values impossible makes software clearer and more reliable.

> except bitfields, of course.

There's no more justification for that than for the other things you object to.

Re: Summary of C/C++ integer rules

#54

Earlier quoted context omitted.

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

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 to hardware (if memory or performance optimization are important, which is rare) and to enforce correctness in my code. You're telling me that the latter is not a valid use of types and I say that's the single-biggest reason I use statically typed languages over dynamically typed languages, when I do so.

But even if that's not the case, why would I use a more general type than I need, when I know the constraints of my code? If I know that negative values are not semantically valid, why not use a type that doesn't allow those? What benefit would I get from not doing that? I mean, why do we have different sizes of integers when all the possible ones I could want can be represented as a machine-native size and I can enforce size constraints in software instead? We could also just use double's for all numbers, like some languages do.

Re: Summary of C/C++ integer rules

#55

This is one of the misconceptions: > sizeof(T) represents the number of 8-bit bytes (octets) needed to store a variable of type T. That's a misconception I had and I've never run into a problem. What's a platform where sizeof works differently? Also, what's the reasoning for sizeof to be an operator rather than a function?

See https://stackoverflow.com/questions/2098149/what-platforms-h... . As for `sizeof` being an operator, well, C doesn't have generics, so it has no choice but to make `sizeof` somehow special. If you don't want to bother supporting platforms where byte is not 8-bit (a reasonable choice I would say), use `int8_t`/`uint8_t` instead. Those types won't exist on platforms that don't have 8-bit bytes.

> If you don't want to bother supporting platforms where byte is not 8-bit (a reasonable choice I would say), use `int8_t`/`uint8_t` instead. Those types won't exist on platforms that don't have 8-bit bytes.

You'll have the issue that, as one of the commenters explained above, `char` is its own thing, independent and separate from `signed char` and `unsigned char` to say nothing of `int8_t` and `uint8_t`. This means that while you can use your own thing for your own functions you can not do so if your values have to interact with libc functions (or most of the ecosystem at large).

If you only want to support platforms using 8-bit chars, you should check CHAR_BIT. That is actually reliable and correct.

Re: Summary of C/C++ integer rules

#56

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…

> Positive values are a particular case of signed values, you can still use signed ints to store positive values. And yet Java's lack of unsigned integers is considered a major example of its (numerous) design errors. > 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. Of course not, there's no need for any type…

Well, you and me are different persons and we don't have to agree on everything. In this case, it seems that we don't agree on anything. But it's still OK, if it works for you ;)

Re: Summary of C/C++ integer rules

#57

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…

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 happen to be positive. Well, in that case it is even worse, because it is incomplete and asymmetric. What's so special about the lower bound of the possible set of values? If it's an index to an array of length N, you'll surely want an integer type whose values cannot exceed N. And this is a can of worms that I prefer not to open...

Re: Summary of C/C++ integer rules

#58

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.

Also, go to the Compiler Explorer and compare the generated code for C++ "num / 2" when num is an int, and when num is an unsigned int.

While there are a few cases where the compiler tends to do a better job of optimizing signed ints than unsigned ints (generally by exploiting the fact that signed integer overflow is undefined), they are not as fundamental as "num / 2". Being forced to write "num >> 1" all over the place whenever I care about performance is basically a dealbreaker for me in many projects; and I haven't even gotten into the additional safety issues introduced by undefined overflow.

Re: Summary of C/C++ integer rules

#59

[Dons language lawyer hat] > floating-point number types will not be discussed at all, because that mostly deals with how to analyze and handle approximation errors that stem from rounding. By contrast, integer math is a foundation of programming and computer science, and all calculations are always exact in theory (ignoring implementations issues like overflow). Integer overflow is no mere implementation issue, any…

> This is no longer true of C++. As of C++20, signed integer types are defined to use two's complement. [0] I don't think C intends to do the same.

As no good language lawyer discussion should be free from pedantry, there is no such thing as "As of C++20". C++20 is just a new version of the C++ standard. Projects that target C++11 or C++14 or C++17 are all still here and won't go away any time soon, and the respective C++ rule still apply to them. Passing a new revision of the C++ standard changes nothing with regards to which rules actually apply to those projects, unless project maintainers explicitly decide to migrate their projects.

Re: Summary of C/C++ integer rules

#60

Earlier quoted context omitted.

The part about lea doesn't seem especially convincing, it's not hard to imagine that pointer arithmetic could be defined such that overflow is still UB, while allowing regular signed integer arithmetic to overflow safely.

I can't say much about this. What I know is that in C, pointer arithmetic is defined in terms of "normal" arithmetic. p[i] is defined as *(p + i). And (p + i) means to offset p by (i * sizeof *p), and that multiplication is computed as the type of i (e.g. (32-bit) int or even smaller type)

That multiplication is entirely implicit, so there is no reason the compiler needs to handle it the same as it handles an explicit multiplication. Given that `p + i` is obviously not an integer addition and it already has much more UB then `i + j`, there is no reason why `i + j` having defined overflow rules needs to mean `p + i` also has them (just like `i + j` is safe for any small enough i and j, while p + i is only meaningful if points within the same object as p (to be fair, its not UB to compute p + i for any i, it's UB to use the value).
Post reply on HN