两个指针的差异类型为ptrdiff_t
,这是一个有符号整数类型,在<stddef.h>
中定义。对于此类型的printf
长度修饰符为t
。两个指针的差异可以直接打印:
printf("pointer difference: %td\n", ptr - A[42]);
子数组维度的大小 (sizeof(A[0]) / sizeof(A[0][0]))
是 size_t
类型,这是由 <stddef.h>
中定义的 sizeof
运算符的无符号结果。
此类型的 printf
长度修饰符为 z
。对象的大小可以直接打印:
printf("array size in bytes: %zu\n", sizeof(A));
ptrdiff_t
是C标准所要求的能够表示至少在-65535
到65535
之间的值,而size_t
必须具有至少0
到65535
的范围。
问题是:ptrdiff_t
除以size_t
的类型是什么?
类型由应用 6.3.1.8通常算术转换 中指定的算术转换确定:
否则(如果两个操作数都具有整数类型),则对两个操作数执行整数提升。然后将以下规则应用于提升的操作数:
如果两个操作数具有相同的类型,则不需要进一步转换。
否则,如果两个操作数都具有带符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的操作数转换为具有更大等级的操作数的类型。
否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则具有带符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。
否则,如果具有带符号整数类型的操作数的类型可以表示无符号整数类型的所有值,则具有无符号整数类型的操作数将转换为具有带符号整数类型的操作数的类型。
否则,两个操作数都将转换为与具有带符号整数类型的操作数相对应的无符号整数类型。
根据实际用于ptrdiff_t
和size_t
的类型,结果的类型可能不同:
整数提升的工作方式是:如果类型int
可以表示其类型的所有值,则将其转换为int
,如果unsigned
可以,则将其转换为unsigned
,否则类型不变。
因此,如果size_t
小于int
,则size_t
会被提升为int
,一个带符号类型,并且除法将作为带符号除法执行,首先将两个操作数转换为int
和ptrdiff_t
的较大类型(在这种情况下很可能也是int
)。
如果size_t
是unsigned int
类型,而ptrdiff_t
是int
类型,则ptrdiff_t
将转换为unsigned int
,并且除法将作为无符号除法执行,并具有结果类型unsigned int
。
相反,如果
size_t
是
unsigned int
类型且
ptrdiff_t
是
long int
类型,则
size_t
操作数会转换为
long int
类型,此除法将成为带符号的除法,并且结果类型为
long int
。
如果
size_t
是
unsigned long int
类型且
ptrdiff_t
是
long int
类型(Linux和OS/X 64位),则将
ptrdiff_t
转换为
unsigned long int
,并且此除法将进行无符号除法,并且结果类型为
unsigned long int
。
如果
size_t
是
unsigned long long int
类型且
ptrdiff_t
是
long long int
类型(Windows 64位),则将
ptrdiff_t
转换为
unsigned long long int
,并且此除法将进行无符号除法,并且结果类型为
unsigned long long int
。
更奇特的架构可能会有其他组合的
size_t
和
ptrdiff_t
类型,从而导致产生更多结果类型的可能性,例如
long long int
。
因此,行计算的类型是实现定义的:它可以是带符号或无符号的,并且不同于
size_t
和
ptrdiff_t
。
有多种方法可以为printf语句产生一致的类型和格式:
使用强制转换:
printf("row number is %d\n", (int)((ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))));
使用中间变量:
int row = (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
printf("row number is %d\n", row);
使用额外操作来强制转换到更大的类型(不完美,因为 size_t
可能比 unsigned long long
更大):
printf("row number is %llu\n", 0ULL + (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
请注意,
printf
格式中的
%zu
和
%td
用于
size_t
和
ptrdiff_t
类型,但由于表达式的类型不一定是
size_t
或
ptrdiff_t
,因此不能使用。更糟糕的是,对于Windows用户来说,Microsoft C运行时库不支持这些标准长度说明符。如Jean-François Fabre所建议的那样,对于使用
gcc
编译C代码并生成本地Windows应用程序的MingW用户,有一个解决方法:在命令行上指定
-D__USE_MINGW_ANSI_STDIO
,告诉
gcc
使用自己的版本的
printf
而不是Microsoft运行时的版本。
最后需要注意的是,如果
ptr
不指向数组的第一行,那么表达式
ptr - A[0]
实际上会导致未定义的行为,如
6.5.6加法运算符所述:当两个指针相减时,它们都必须指向同一个数组对象的元素,或者是数组对象的最后一个元素之后的位置;结果是两个数组元素下标之间的差。
-Wconversion
选项,这个语句int row = (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
会被警告。 - alk%zu
在Microsoft实现的printf
中不起作用。你必须启用-D__USE_MINGW_ANSI_STDIO
来使用gcc内置的printf,这样就能正常工作了(我找了很久才找到)。 - Jean-François Fabre