什么是(ptr - A [0])/(sizeof(A [0])/ sizeof(A [0] [0]))的类型?

chq*_*lie 4 c c++ arrays printf

我有一个2D数组A和一个指向ptr它内部的指针.我知道如何计算行号,但表达式的实际类型似乎是不可移植的:

#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;
}
Run Code Online (Sandbox Code Playgroud)

在OS/X上,clang编译器以这种方式抱怨:

warning: format specifies type 'int' but the argument has type 'unsigned long' [-Wformat]
Run Code Online (Sandbox Code Playgroud)

在Linux上,gcc发出类似的警告,但在Windows上,我得到一个不同的诊断:

warning: format specifies type 'int' but the argument has type 'unsigned long long' [-Wformat]
Run Code Online (Sandbox Code Playgroud)

这个表达式的实际类型是什么?

有没有办法将表达式传递给printf没有丑陋的演员?

chq*_*lie 7

2个指针的差异有类型ptrdiff_t,定义的有符号整数类型<stddef.h>.printf这种类型的长度修饰符是t.2个指针的差异可以直接打印:

printf("pointer difference: %td\n", ptr - A[42]);
Run Code Online (Sandbox Code Playgroud)

子数组维的大小(sizeof(A[0]) / sizeof(A[0][0]))size_t无符号类型f,它是sizeof运算符定义的结果<stddef.h>.

printf这种类型的长度修饰符是z.对象的大小可以直接打印:

printf("array size in bytes: %zu\n", sizeof(A));
Run Code Online (Sandbox Code Playgroud)

ptrdiff_t是由C标准所要求的,以便能够之间代表值-6553565535至少,而size_t必须有一个范围内的至少的065535.

现在的问题是:什么是一个分裂的类型ptrdiff_tsize_t

通过应用6.3.1.8常规算术转换中指定的算术转换来确定类型:

否则(如果两个操作数都具有整数类型),则对两个操作数执行整数提升.然后将以下规则应用于提升的操作数:

  • 如果两个操作数具有相同的类型,则不需要进一步转换.否则,如果两个操作数都具有有符号整数类型或两者都具有无符号整数类型,则具有较小整数转换等级类型的操作数将转换为具有更高等级的操作数的类型.

  • 否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则具有有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型.

  • 否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数将转换为带有符号整数类型的操作数的类型.

  • 否则,两个操作数都转换为无符号整数类型,对应于带有符号整数类型的操作数的类型.

根据所使用的实际类型ptrdiff_tsize_t,结果的类型可以是不同的:

整数提升以这种方式工作:如果type int可以表示其类型的所有值,则转换为int(如果unsigned有)转换为unsigned,否则类型不变.

因此,如果size_t小于int,size_t被提升到int,一个有符号的类型,划分为一个符号除法进行的,这两个操作数首先被转换为较大型intptrdiff_t(很可能是int以及在这种情况下).

如果size_t是type unsigned int并且ptrdiff_t是type int,ptrdiff_t则转换为unsigned int,并且除法作为带有结果类型的无符号除法执行unsigned int.

相反,如果size_tunsigned intptrdiff_t是类型long int,size_t操作数将转换为类型long int,则除法是带符号的除法,结果类型为long int.

如果size_tunsigned long intptrdiff_t是类型long int(Linux和OS/X 64位),ptrdiff_t则转换为unsigned long int,并且除法是带有结果类型的无符号除法unsigned long int.

如果size_tunsigned long long intptrdiff_t是类型long long int(Windows 64位),ptrdiff_t则转换为unsigned long long int,并且除法是带有结果类型的无符号除法unsigned long long int.

更奇特的结构可能对其他类型的组合size_tptrdiff_t,导致了结果类型更多的可能性,如long long int.

因此,该行计算的类型实现定义:它可以被符号或无符号,并从两个不同的size_tptrdiff_t.

有多种方法可以为printf语句生成一致的类型和格式:

使用演员:

printf("row number is %d\n", (int)((ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))));
Run Code Online (Sandbox Code Playgroud)

使用中间变量:

int row = (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
printf("row number is %d\n", row);
Run Code Online (Sandbox Code Playgroud)

使用额外的操作来强制更大的类型(不完美,size_t可能大于unsigned long long):

printf("row number is %llu\n", 0ULL + (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
Run Code Online (Sandbox Code Playgroud)

请注意,不能使用for 和for 的printf格式%zu,因为表达式的类型不一定不是.更糟糕的是,对于Windows用户来说,Microsoft C运行时库不支持这些标准长度说明符.正如Jean-FrançoisFabre所建议的,MingW用户可以使用编译C代码并生成本机Windows应用程序:在命令行上指定使用自己的版本而不是Microsoft运行时版本.size_t%tdptrdiff_tsize_tptrdiff_tgcc-D__USE_MINGW_ANSI_STDIOgccprintf


最后注意:ptr - A[0]如果ptr不指向数组的第一行内部,则表达式实际上会调用未定义的行为,如6.5.6 Additive运算符中所指定:

  1. 当减去两个指针时,两个指针都指向同一个数组对象的元素,或者指向数组对象的最后一个元素的元素; 结果是两个数组元素的下标的差异.