C语言 在 printf 参数中提升类型是否危险



我的问题源于在尝试为多个位深度平台(例如 32/64)构建时尝试使用 printf 来记录内容。

一个不断抬头的问题是试图在多个架构上打印整数。 在 32 位上,它会像

printf(" my int: %dn", myInt);

但是在 64 位上,它必须更改为

print (" my int: %ldn", (long)myInt);

我有两个相关的问题:

  1. 我的第一个想法是,当你告诉printf打印一个变量,给它一个格式,它会查看该变量的地址,并获取该格式所需的尽可能多的字节。 起初这似乎是一个大问题。例如,如果你有一个变量 myChar,它是一个字符(1 字节),但使用了格式说明符 %d,这将告诉 printf 转到 myChar 的地址并获取接下来的 4 个字节以将其视为整数。 如果是这种情况,似乎 printf 会从相邻变量中抓取垃圾日期(因为它抓取了 4 个字节,但实际值只有 1 个字节)。 然而,情况似乎并非如此。通过使用 myChar 并指定 %d,printf 抓取 1 个字节,然后用 0 填充前 3 个字节。 我的理解在这里正确吗?

  2. 如果上述情况属实,那么始终将变量提升到其最大值以避免在 32/64 位情况下出现的问题类型是否有任何真正的危害。 例如,如果你有一个短变量myShort和一个int变量myInt,那么将它们打印

    为:printf("myShort %ld", (long)myShort); printf("myInt %ld", (long)myInt);

感谢您的任何澄清。

关于printf:在您放置的情况下,"%d"必须根据规范处理平台定义的"int"数据类型。无论是 32 位、64 位还是 128 位线性 AS/400 值都没有关系。如果要将值提升为更大的字段类型(并将该提升与相关格式字符串粒子匹配),您当然可以自由地这样做,

int a=0;
printf("%ld", (long)a);

当然是使用促销来定义的行为。

我认为你问题的真正症结在于以下情况,以及强制晋升是否可以"解决"出现的任何问题。例如:

char ch = 'a';
printf("%d", ch);

或者呢:

char ch = 'a';
printf("%ld", (long)ch);

或者也许这个(这是你似乎试图避免的真实情况):

char ch = 'a';
printf("%ld", ch);

其中第一个将起作用,但只是因为在 va-arg 列表中推送的任何堆栈的最小大小是int的平台大小。编译器会自动将值提升为 int。由于"%d"需要一个平台int因此所有平台都会看起来很好。

第二个将始终工作并完全受支持。从charlong有一个明确而明确的晋升。即使long是 64 位(或更大),它仍然可以工作。

第三个是UB一路走来。printf正在寻找long,并且只会显示int的字节。如果这似乎在您的平台上"有效",请检查您的平台宽度是否有intlong。它可能只是因为您的平台longint是相同的位宽而"工作"。当将代码移植到它们不存在的平台时,它会带来有趣的惊喜,并且由于它是通过 va-arg 推送的,因此在真正不同的宽度进入播放之前,您不会看到它。

话虽如此,现在将实际地址扔给某些东西(任何东西,真的),例如scanf所要求的地址,我们正在研究完全不同的东西。

int val;
sscanf("%ld",&val);

这是一个等待发生的赛段错误。就像上面一样,如果您的平台long和平台int宽度相同,您将永远不会知道。将此代码带到一个盒子中,其中longint的大小不同,并为随后的核心文件的 gdb 加载做好准备。

你说:

一个不断抬头的问题是试图在多个架构上打印整数

尝试通过传入不属于该类型大小的值来解决类型问题是否危险,是的。 这就是编译器警告您的原因。似乎给您带来问题的可移植性概念并不是为了让printf满意。

它旨在使您的程序运行而不会在多个体系结构上崩溃。 如果您有特定于平台的代码,则应使用 #ifdef 宏来解决它。

否则,您将掷骰子,尝试在内存级别类型转换上分层。

printf 是一种方便而不是类型转换方法。

看来你专注于整数 - 你可能会侥幸逃脱。但总的来说,我不会依赖这样的技术。

bools/_Boolscharsshorts在传递给像printf()这样的可变参数函数时首先转换为int(如果此转换保留了该值,否则转换为unsigned int)。同样floats转换为doubles

因此,如果您传递小于int的内容,printf()将毫无问题地抓取整个(unsigned) int(除非传递的值实际上是unsigned int并且您用%d而不是%u打印它,您将获得未定义的行为)。

其他类型,AFAIR,不进行此类转换。

这一行:

print (" my int: %ldn", (long)myInt);

没有通过这条线给你买任何东西:

printf(" my int: %dn", myInt);

两者都是有效的,结果实际上是相同的。唯一的区别是前者可能会导致更大的代码和更长的执行时间(如果sizeof(long) >= sizeof(int))。

  1. 参数在堆栈中传递,每个条目具有固定宽度(32 或 64)位。编译器将整数、字符、短整型"投射"到架构的本机宽度,或者在 32 架构的双精度(或长整型)的情况下,它从堆栈中分配两个插槽。"填充"要么用零完成,要么将变量的符号位复制到剩余位。(称为符号位扩展)

  2. 升级到 64 位
  3. 的一个缺点是嵌入式系统缺乏兼容性,这些系统通常不提供 64 位打印。此外,这意味着在 32 位系统中会有一些性能损失,因为顶部 32 位总是被传递和转换(涉及 64 位宽除以 10),没有任何实际使用。然而,更大的问题属于软件工程领域:"未来兼容"日志是否给人一种错误的希望,即系统的所有计算和所有输入在32位系统上都以64位模式工作。

(long)在 32 位体系结构中并不意味着 64 位。这是用(长长)标注的。

最新更新