← Back to context

Comment by rwmj

9 hours ago

Slighty off-topic, why is he using ptrdiff_t (instead of size_t) for the cap & len types?

From one of his other blogposts. "Guidelines for computing sizes and subscripts"

  Never mix unsigned and signed operands. Prefer signed. If you need to convert an operand, see (2).

https://nullprogram.com/blog/2024/05/24/

https://www.youtube.com/watch?v=wvtFGa6XJDU

  • I still don't understand how these arguments make sense for new code. Naturally, sizes should be unsigned because they represent values which cannot be unsigned. If you do pointer/size arithmetic, the only solution to avoid overflows is to overflow-check and range-check before computation.

    You cannot even check the signedness of a signed size to detect an overflow, because signed overflow is undefined!

    The remaining argument from what I can tell is that comparisons between signed and unsigned sizes are bug-prone. There is however, a dedicated warning to resolve this instantly.

    It makes sense that you should be able to assign a pointer to a size. If the size is signed, this cannot be done due to its smaller capacity.

    Given this, I can't understand the justification. I'm currently using unsigned sizes. If you have anything contradicting, please comment :^)

    • C offers a different solution to the problem in Annex K of the standard. It provides a type `rsize_t`, which like `size_t` is unsigned, and has the same bit width, but where `RSIZE_MAX` is recommended to be `SIZE_MAX >> 1` or smaller. You perform bounds checking as `<= RSIZE_MAX` to ensure that a value used for indexing is not in the range that would be considered negative if converted to a signed integer. A negative value provided where `rsize_t` is expected would fail the check `<= RSIZE_MAX`.

      IMO, this is a better approach than using signed types for indexing, but AFAIK, it's not included in GCC/glibc or gnulib. It's an optional extension and you're supposed to define `__STDC_WANT_LIB_EXT1__` to use it.

      I don't know if any compiler actually supports it. It came from Microsoft and was submitted for standardization, but ISO made some changes from Microsoft's own implementation.

      https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1173.pdf#p...

      https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1225.pdf

      1 reply →

    • "Naturally, sizes should be unsigned because they represent values which cannot be unsigned."

      Unsigned types in C have modular arithmetic, I think they should be used exclusively when this is needed, or maybe if you absolutely need the full range.

    • Pointer arithmetic that could overflow would probably involve a heap and therefore be less likely to require a relative, negative offset. Just use the addresses and errors you get from allocation.

      1 reply →

    • I dont know either.

      int somearray[10];

      new_ptr = somearray + signed_value;

      or

      element = somearray[signedvalue];

      this seems almost criminal to how my brain does logic/C code.

      The only thing i could think of is this:

      somearray+=11; somearray[-1] // index set to somearray[10] ??

      if i'd see my CPU execute that i'd want it to please stop. I'd want my compiler to shout at me like a little child, and be mean until i do better.

      -Wall -Wextra -Wextra -Wpedantic <-- that should flag i think any of these weird practices.

      As you stated tho, i'd be keen to learn why i am wrong!

      3 replies →

    • > It makes sense that you should be able to assign a pointer to a size. If the size is signed, this cannot be done due to its smaller capacity.

      Why?

      By the definition of ptrdiff_t, ISTM the size of any object allocated by malloc cannot be out of bounds of ptrdiff_t, so I'm not sure how can you have a useful size_t that uses the sign bit?

Skeeto and Stroustrup are a bit confused about valid index types. They prefer signed, which will lead to overflows on negative values, but have the advantage of using only half of the valid ranges, so there's more heap for the rest. Very confused