【问题标题】:Is it UB to give a char argument to printf where printf expects a int?UB 是否给 printf 一个 char 参数,而 printf 需要一个 int?
【发布时间】:2021-01-02 14:33:02
【问题描述】:

我是否正确理解该程序导致 UB 的标准:

#include <stdio.h>

int main(void)
{
    char a = 'A';
    printf("%c\n", a);
    return 0;
}

当它在sizeof(int)==1 &amp;&amp; CHAR_MIN==0的系统上执行时?

因为如果a 是无符号的并且具有与int 相同的大小(1),它将被提升为unsigned int [1] (2),而不是int,因为a int 不能代表 char 的所有值。格式说明符 "%c" 需要 int [2] 并且在 printf() 中使用错误的签名会导致 UB [3]。

ISO/IEC 9899 中针对 C99 的相关引用

[1] 根据 C99 6.3.1.1:2 升级为int

如果int 可以表示原始类型的所有值,则该值为 转换为int;否则,它将转换为unsigned int。 这些称为整数促销。所有其他类型都是 整数促销保持不变。

[2] 格式说明符 "%c" 需要 int 参数,C99 7.19.6.1:8 c

如果不存在 l 长度修饰符,则 int 参数将转换为 unsigned char,然后写入结果字符。

[3] 在fprintf() (3) 中使用错误的类型,包括错误的签名,根据 C99 7.19.6.1:9 导致 UB:

... 如果任何参数不是对应的正确类型 转换规范,行为未定义。

va_arg 宏给出了具有不同符号的相同类型的例外,但printf() 没有,并且没有要求printf() 使用va_arg (4)。

脚注: (标有 (n))

  1. 这意味着INT_MAX==SCHAR_MAX,因为char 没有填充。

  2. 另请参阅此问题:Is unsigned char always promoted to int?

  3. 同样的规则适用于printf(),参见 C99 7.19.6.3:2

  4. 另请参阅此问题:Does printf("%x",1) invoke undefined behavior?

【问题讨论】:

  • @einpoklum 如果没有促销,当sizeof(int)&gt;1(绝大多数系统)时,UB 会为"%c" 提供char,因为%c 期望int。格式说明符 %c 没有 hh 长度修饰符,因此您必须将其强制转换为 int 而无需提升。
  • @12431234123412341234123 :如果没有提升,printf() 将被写入,因此 "%c" 期望 char
  • @12431234123412341234123 你是对的。我忘记了这种可能性。

标签: c language-lawyer undefined-behavior integer-promotion


【解决方案1】:

TL;DR 没有 UB(无论如何在我的解释中)。

6.2.5 类型
6. 对于每一种有符号整数类型,都有一个对应的(但不同的)无符号整数类型(用关键字unsigned指定),它使用相同的存储量(包括符号信息)并且具有相同的对齐要求。
9.有符号整数类型的非负值范围是对应无符号整数类型的一个子范围,相同值在每种类型中的表示是相同的41)
41) 相同的表示和对齐要求意味着作为函数的参数、函数的返回值和联合成员的可互换性。

还有

7.16.1.1 va_arg 宏
2 va_arg 宏扩展为具有指定类型和调用中下一个参数的值的表达式。 [...]如果没有实际的下一个参数,或者类型与实际的下一个参数的类型不兼容(根据默认参数提升),则行为未定义,但以下情况除外:

  • 一种是有符号整数类型,另一种是对应的无符号整数类型,值在两种类型中都可以表示;

7.21.6.8 vfprintf 函数
288) [...] 函数 vfprintf、vfscanf、vprintf、vscanf、vsnprintf、vsprintf 和 vsscanf 调用 va_arg 宏 [...]

因此,只要值在范围内,无符号类型不是“对应(有符号)转换规范的错误类型”是理所当然的。

主要编译器不会警告有符号/无符号格式规范不匹配的事实,即使它们确实警告其他不匹配,即使相应类型在给定平台上具有相同的表示形式(例如longlong long)。

【讨论】:

  • 很好的答案,我不知道脚注 41(或 C99 中的 31)。您可以在这里发布相同的答案:stackoverflow.com/questions/4664100/… 吗?因为它也适用于那里。但我只认为第一部分是相关的,因为我们谈论的是使用... 而不是vprintf()va_listprintf()
  • @12431234123412341234123 printf、fprintf 和 vfprintf 都被指定为等效的模数明显参数替换。
  • @12431234123412341234123 如果两个问题有相同的答案,那么一个是另一个的重复,应该这样关闭。
  • 你认为我的问题是这个问题stackoverflow.com/questions/4664100/… 的重复吗?如果不是,您认为您的答案不适用于其他问题吗?我认为“如果两个问题有相同的答案,那么一个是重复的”这一论点是不正确的,因为相同的答案可以同时回答 2 个不同的问题。但是,如果您不想发布相同的答案,我会这样做。
  • @12431234123412341234123 我认为复制不同问题的答案是错误的。信息应链接而不是复制。将其中一个问题作为重复项关闭是链接到正确答案的一种方法。不过,我完全不确定这两者中的哪一个应该关闭。
【解决方案2】:

程序可能有未定义的行为,也可能没有取决于实现的特征

例如,执行的程序

int x = 32767;
x++;

(否则定义明确)在 INT_MAX > 32767 的实现上具有明确定义的行为,否则为未定义的行为。

你的程序:

#include <stdio.h>

int main(void)
{
  char a='A';
  printf("%c\n",a);
  return 0;
}

对于INT_MAX &gt;= CHAR_MAX 的任何托管实现都有明确定义的行为。在任何此类实现中,'A' 的值都会提升为 int,这是 %c 所期望的。

如果INT_MAX &lt; CHAR_MAX(这意味着普通的char 是无符号的,而CHAR_BIT &gt;= 16),则a 的值将提升为unsigned int。 N1570 7.21.6.1p9:

如果任何参数不是对应的正确类型 转换规范,行为未定义。

暗示这具有未定义的行为。

在实践中,(a) 此类实现很少见,可能根本不存在(我听说的 CHAR_BIT &gt; 8 的现有 C 实现仅用于 DSPs,并且此类实现可能是独立的),以及 (b)任何此类实现都可能旨在优雅地处理此类情况。

【讨论】:

  • 此外,在我使用的唯一实现中,char 是 16 位,它是签名的。我想知道是否有任何困难指定char 可能只是无符号所有可能的位模式代表char 的不同有效值(这意味着虽然signed char 可能有陷阱或负零表示,char 可以不是),并且所有这些值都在int的范围内?
  • @supercat 你用过什么C实现16位char
  • 我编写了一个嵌入式应用程序,其中包括 TMS 32050 系列上的 TCP/IP 堆栈和闪存文件系统。
  • TMS 32050 是一个 DSP。
  • 确实如此。但是,添加正确的 I/O,它可以自行控制嵌入式系统。
【解决方案3】:

我是否理解这个程序导致 UB 的标准正确:

#include <stdio.h>

int main(void)
{
  char a='A';
  printf("%c\n",a);
  return 0;
}

当它在 sizeof(int)==1 && CHAR_MIN==0 的系统上执行时?

这将是对该标准的合理解释。但是,如果生成了具有这种类型特征组合的实现以供真正使用,我完全相信它将为%c 指令提供适当的支持——作为扩展,如果有人想解释它大大地。然后,示例程序将具有关于该实现的明确定义的行为,无论 C 标准是否也被解释为定义该行为。我想我将实施质量问题归结为“供真正使用”。

【讨论】:

  • 我认为该示例只会在使用大于 INT_MAX 的值表示字符“A”的平台上导致与char 相关的问题。虽然在这种情况下可以设计一个实现,但没有现实的理由担心这些事情,因为给定任何源文本,人们可以设计一个“符合 C 实现”来无意义地处理它。
  • 谢谢,@supercat。我希望这个答案已经传达了“没有现实的理由担心”。当然这就是我想要表达的意思。
猜你喜欢
  • 2020-08-22
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 2013-08-05
  • 1970-01-01
  • 2017-05-06
  • 1970-01-01
  • 2023-01-26
相关资源
最近更新 更多