What is the type of (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))?
arrays, c, c++, printf
Solution
The difference of 2 pointers has type `ptrdiff_t`, a signed integer type defined in `<stddef.h>`. The `printf` length modifier for this type is `t`. The difference of 2 pointers can be printed directly with:
printf("pointer difference: %td\n", ptr - A[42]);
The size of the subarray dimension `(sizeof(A[0]) / sizeof(A[0][0]))` is `size_t`, the unsigned type f the result of the `sizeof` operator defined in `<stddef.h>`.
The `printf` length modifier for this type is `z`. The size of an object can be printed directly with:
printf("array size in bytes: %zu\n", sizeof(A));
`ptrdiff_t` is required by the C standard to be able to represent values between `-65535` and `65535` at least, while `size_t` must have a range of at least `0` to `65535`.
The question is: what is the type of the division of a `ptrdiff_t` by a `size_t`?
The type is determined by applying the arithmetic conversions as specified in 6.3.1.8 Usual arithmetic conversions:
Otherwise (if both operands have integer types), the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed. Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
Depending on the actual types used for `ptrdiff_t` and `size_t`, the type of the result can be different:
Integer promotion works this way: if type `int` can represent all values of its type, it is converted to `int`, if `unsigned` does, it is converted to `unsigned`, otherwise the type is unchanged.
Hence if `size_t` is smaller than `int`, `size_t` gets promoted to `int`, a signed type, and the division is performed as a signed division, both operands are first converted to the larger type of `int` and `ptrdiff_t` (very likely to be `int` as well in this case).
If `size_t` is type `unsigned int` and `ptrdiff_t` is type `int`, the `ptrdiff_t` is converted to `unsigned int` and the division is performed as an unsigned division with a result type `unsigned int`.
Conversely, if `size_t` is `unsigned int` and `ptrdiff_t` is type `long int`, the `size_t` operand is converted to type `long int`, the division is then a signed division and the resulting type is `long int`.
If `size_t` is `unsigned long int` and `ptrdiff_t` is type `long int` (Linux and OS/X 64-bit), the `ptrdiff_t` is converted to `unsigned long int` and the division is an unsigned division with resulting type `unsigned long int`.
If `size_t` is `unsigned long long int` and `ptrdiff_t` is type `long long int` (Windows 64-bit), the `ptrdiff_t` is converted to `unsigned long long int` and the division is an unsigned division with resulting type `unsigned long long int`.
More exotic architectures might have other combinations for types `size_t` and `ptrdiff_t`, resulting in more possibilities for the resulting type, such as `long long int`.
As a consequence, the type of the row computation is implementation defined: it can be signed or unsigned and different from both `size_t` and `ptrdiff_t`.
There are multiple ways to produce a consistent type and format for the printf statement:
Using a cast:
printf("row number is %d\n", (int)((ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))));
Using an intermediary variable:
int row = (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
printf("row number is %d\n", row);
Using an extra operation to force a larger type (not perfect as `size_t` could be larger than `unsigned long long`):
printf("row number is %llu\n", 0ULL + (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
Note that the `printf` formats `%zu` for `size_t` and `%td` for `ptrdiff_t` cannot be used since the type of the expression is not necessarily `size_t` not `ptrdiff_t`. To make matters worse for Windows users, these standard length specifiers are not supported by the Microsoft C runtime library. As suggested by Jean-François Fabre, there is a work-around for MingW users who compile C code with `gcc` and generate native Windows applications: specifying `-D__USE_MINGW_ANSI_STDIO` on the command line tells `gcc` to use its own version of `printf` instead of that of the Microsoft runtime.
Final note: the expression `ptr - A[0]` actually invokes undefined behavior if `ptr` does not point inside the first row of the array, as specified in 6.5.6 Additive operators:
- When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements.
Problem
I have a 2D array `A` and a pointer `ptr` pointing somewhere inside it. I know how to compute the row number but the actual type of the expression seems non-portable: ``` #include <stdio.h> int main(void) { int A[100][100]; int *ptr = &A[42][24]; printf("row number is %d\n", (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))); printf("col number is %d\n", (ptr - A[0]) % (sizeof(A[0]) / sizeof(A[0][0]))); return 0; } ``` On OS/X, the `clang` compiler complains this way: ``` warning: format specifies type 'int' but the argument has type 'unsigned long' [-Wformat] ``` On Linux, `gcc` gives a similar warning, but on Windows, I get a different diagnostic: ``` warning: format specifies type 'int' but the argument has type 'unsigned long long' [-Wformat] ``` What is the actual type of this expression? Is there a way to pass the expression to `printf` without an ugly cast?