Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the type of (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))?

Tags:

c++

arrays

c

printf

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?

like image 545
chqrlie Avatar asked Mar 10 '23 19:03

chqrlie


1 Answers

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:

  1. 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.
like image 170
chqrlie Avatar answered Mar 15 '23 22:03

chqrlie