【问题标题】:What is the type of (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0]))?(ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])) 的类型是什么?
【发布时间】:2017-03-12 18:20:23
【问题描述】:

我有一个二维数组 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;
}

在 OS/X 上,clang 编译器会这样抱怨:

warning: format specifies type 'int' but the argument has type 'unsigned long' [-Wformat]

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

warning: format specifies type 'int' but the argument has type 'unsigned long long' [-Wformat]

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

有没有一种方法可以将表达式传递给printf 而不会产生丑陋的演员表?

【问题讨论】:

    标签: c++ c arrays printf


    【解决方案1】:

    2个指针的区别是ptrdiff_t类型,&lt;stddef.h&gt;中定义的有符号整数类型。此类型的 printf 长度修饰符是 t。 2个指针的差值可以直接打印:

    printf("pointer difference: %td\n", ptr - A[42]);
    

    子数组维度(sizeof(A[0]) / sizeof(A[0][0]))的大小是size_t,无符号类型f是&lt;stddef.h&gt;中定义的sizeof运算符的结果。

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

    printf("array size in bytes: %zu\n", sizeof(A));
    

    C 标准要求ptrdiff_t 至少能够表示-6553565535 之间的值,而size_t 必须具有至少065535 的范围。

    问题是:ptrdiff_tsize_t 划分的类型是什么?

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

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

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

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

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

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

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

    整数提升这样工作:如果类型int可以表示其类型的所有值,则将其转换为int,如果unsigned可以,则将其转换为unsigned ,否则类型不变。

    因此,如果size_t 小于int,则size_t 被提升为有符号类型int,并且除法作为有符号除法执行,两个操作数首先转换为较大的@ 类型987654351@ 和 ptrdiff_t(在这种情况下也很可能是 int)。

    如果size_tunsigned int 类型且ptrdiff_tint 类型,则ptrdiff_t 将转换为unsigned int,并且除法将作为结果类型为unsigned int 的无符号除法执行。

    相反,如果size_tunsigned intptrdiff_tlong int 类型,则size_t 操作数将转换为long int 类型,则除法为有符号除法,结果类型为@987654367 @。

    如果size_tunsigned long int 并且ptrdiff_tlong int 类型(Linux 和OS/X 64 位),则ptrdiff_t 将转换为unsigned long int,并且除法是无符号除法,结果为输入unsigned long int

    如果size_tunsigned long long int 并且ptrdiff_tlong 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]))));
    

    使用中间变量:

    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 用于size_t%td 用于ptrdiff_t,因为表达式的类型不一定是size_t 而不是ptrdiff_t。更糟糕的是,对于 Windows 用户来说,这些标准长度说明符不受 Microsoft C 运行时库的支持。正如 Jean-François Fabre 所建议的,对于使用 gcc 编译 C 代码并生成本机 Windows 应用程序的 MingW 用户,有一个解决方法:在命令行上指定 -D__USE_MINGW_ANSI_STDIO 告诉 gcc 使用自己的 @ 版本987654402@ 而不是 Microsoft 运行时的那个。


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

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

    【讨论】:

    • 使用-Wconversion 会警告int row = (ptr - A[0]) / (sizeof(A[0]) / sizeof(A[0][0])));
    • @alk:是的,如果所有关于转换的警告都被启用,我不知道如何避免强制转换。
    • @chqrlie 我们昨天的问题启发了你我明白了:) 干得好。也许您可以提到%zu 不适用于printf 的Microsoft 实现。您必须启用 -D__USE_MINGW_ANSI_STDIO 才能使用 gcc 内置的 printf 并且它会起作用(我花了一段时间才找到它)
    • @Jean-FrançoisFabre:确实不错!对于那些仍然应付 Windows 过时的 C 环境的人来说非常有用。在 DOS 提示符下使用各种 Unix 实施例 26 年后,我个人在 8 年前放弃了。 Donc je compatis。答案已更新。
    【解决方案2】:

    这很难说,而且我认为,没有硬性规定可以定义对所有平台都一致的类型。这种表达式没有标准库 typedef。

    如果您使用 C++,请考虑使用 auto 声明此类类型,并使用 std::cout 打印此类类型的值:依靠编译器为 选择适当的重载

    【讨论】:

    • 对于这个问题确实有C++特定的解决方案。我倾向于支持 printf() 之类的转换,&lt;&lt; 重载的语法很糟糕,但可扩展且可识别类型。
    • @alk ptrdiff_t 自 C89 以来一直存在。
    猜你喜欢
    • 2013-08-24
    • 1970-01-01
    • 2014-01-05
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    • 2018-03-21
    相关资源
    最近更新 更多