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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With