1

In C99, one of the possible format specifiers is "%zi", which according to cppreference should correspond to a "signed size_t". On Linux systems, I use ssize_t from sys/types.h and that works. But - on Windows, we don't have that. Plus - the fact that it has worked for me might just be a fluke.

How can I determine, for certain and in a portable way, the type I'm supposed to pass for "%zi" in a printf()/sprintf() call?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    If the C specification says `%zi` is for "signed `size_t`" then it's for signed `size_t`. Always. What the specification says *is* portable, even if there isn't such a type predefined by some implementations. – Some programmer dude Sep 22 '21 at 11:33
  • What does "on Windows we don't have that" mean? The available data types are depending on the compiler. – Gerhardh Sep 22 '21 at 12:31
  • Normally the process goes the other way around. You have some data type and pick the proper format specifier. Why would you want to use `%zi` and search for a matching variable type? – Gerhardh Sep 22 '21 at 12:32

1 Answers1

3

How can I determine, for certain and in a portable way, the type I'm supposed to pass for "%zi" in a printf()/sprintf() call?

You cannot do this in a certain and portable way.

The C standard acknowledges the possible existence1 of a signed integer type corresponding to size_t but does not give it a name or provide a portable method of identifying it. Like many things, this is left as an extension that is in some C implementations and not others.

Footnote

1 C 2018 6.2.5 6 requires that, for each signed integer type, there be a corresponding unsigned integer type, but the converse is not required. (At least not explicitly; I have not checked whether, for example, the usual arithmetic conversions imply it.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Well that's silly on the standard committee's part :-( So, would you do anything beyond trying to find the platform's `ssize_t` somewhere? – einpoklum Sep 22 '21 at 12:51
  • Hmm the closest to an official `ssize_t` seems to be `ptrdiff_t` however due to segmentation those could be different (although realistically that's unlikely). Given c++23 is getting an [official literal suffix for `ptrdiff_t`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0330r8.html) I'd be surprised if the C committee didn't follow suit and add that and `printf` support. – Mgetz Sep 22 '21 at 13:02
  • @einpoklum: What are you trying to do? As phrased, your question is “What type should we pass for `%zi`?” But who has a `printf` conversion specification first and then picks the type? Usually we have some thing we want to print, and we choose the conversion specification for its type. – Eric Postpischil Sep 22 '21 at 13:02
  • @EricPostpischil: A guy working on [a `printf()` implementation](https://github.com/eyalroz/printf) :-P – einpoklum Sep 22 '21 at 13:14
  • @einpoklum: Ah, but still the wrong question. The question is not what to pass for `%zi` but what to receive for `%zi`. For a signed integer type, you are allowed to ask `va_arg` for the corresponding unsigned type. So the `printf` implementation can use `va_arg(ap, size_t)` to get a `size_t` instead of whatever signed type the caller passed. C 2018 7.16.1.1 2 supports this except that the value should be representable in both types. So, technically, the behavior would be undefined when the value is negative… – Eric Postpischil Sep 22 '21 at 13:28
  • … However, obviously the compiler is not going to care; the choice about where and how to pass the argument is going to be made at compile time, and so is the choice about how to fetch it with `va_arg`. Theoretically, a compiler could insert code to crash if the value is negative or the compiler might load the argument with some move-zero-high instruction (move 32 bits into a 64-bit register with no sign extension). The former is not realistic. The latter you could compensate for in various ways. – Eric Postpischil Sep 22 '21 at 13:31
  • @EricPostpischil: Those are both sides of the same question really... but fair enough. I'll try to look at that clause of the standard. – einpoklum Sep 22 '21 at 13:32
  • One option might be `intmax_t value; switch (sizeof(size_t)) { case 4: value = va_arg(ap, int32_t); break; case 8: value = va_arg(ap, int64_t); break; … }`. Technically this could yield undefined behavior because `int32_t` is a different type from the 32-bit signed `size_t` even though they have actually the same size and representation, but that is again unrealistic. And, as an implementor of a standard library function, one would be entitled to assert the compiler must be somewhat cooperative. – Eric Postpischil Sep 22 '21 at 13:33
  • "For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned)" So clearly this doesn't apply to `size_t` since it isn't written as `unsigned size_t`. This text from $6 must refer to the standard integer types defined in $4. My take is that `size_t` and `ptrdiff_t` are among the "extended integer types". – Lundin Sep 22 '21 at 13:49
  • @Mgetz I wouldn't assume that `ptrdiff_t` is a corresponding signed version of `size_t`. If you check out this: https://stackoverflow.com/questions/42574890/why-is-the-maximum-size-of-an-array-too-large, we found out that this is definitely not the case - a certain common implementation had chosen to make `size_t` half the size of `ptrdiff_t`, supposedly to cover all the requirements of the `ptrdiff_t` type. – Lundin Sep 22 '21 at 13:55
  • @Lundin I mentioned that in regards to segmentation . Not that specific case but it still stands. Technically speaking a true `ssize_t` does end up being for all practical purposes `ptrdiff_t` on a linear implementation because AFAIK no implementation has a 65bit integer for `ssize_t` to correspond to a 64bit `size_t`. – Mgetz Sep 22 '21 at 14:01
  • @Mgetz In the above link they solved it by making the largest allocatable size effectively 63 bits wide. So if you'd use 64 bit `size_t` you would still only be able to meaningfully use 63 bits of it. – Lundin Sep 22 '21 at 14:11