根据C标准(6.5.2.2第6段)
如果表示被调用函数的表达式具有不包含a的类型 原型,对每个参数执行整数提升,并对其进行参数化 将float类型提升为double。这些被称为默认参数 促销活动。如果参数的数量不等于参数的数量,则 行为未定。如果函数是用包含原型的类型定义的,那么 原型以省略号(,...)或后面的参数类型结束 促销与参数的类型不兼容,行为未定义。 如果函数是使用不包含原型的类型和类型来定义的 促销后的参数与之后的参数不兼容 促销,行为未定,除以下情况外:
- 一个提升类型是有符号整数类型,另一个提升类型是 相应的无符号整数类型,该值可在两种类型中表示;
- 这两种类型都是指向字符类型的限定或不合格版本的指针 空隙。
因此,一般来说,只要传递的值适合两种类型,将int
传递给期望unsigned int
的变量函数(反之亦然)就没有错。但是,printf
的规范读取(7.19.6.1第9段):
如果转换规范无效,则行为未定义。如果有任何论据 不是相应转换规范的正确类型,行为是 理解过程科幻奈德。
对签名/无符号不匹配没有例外。
这是否意味着printf("%x", 1)
调用未定义的行为?
答案 0 :(得分:15)
我认为技术上未定义,因为%x
的“正确类型”被指定为unsigned int
- 正如您所指出的,此处签名/无符号不匹配也不例外。
printf
的规则适用于更具体的情况,因此会覆盖一般情况的规则(对于特定覆盖常规的另一个示例,通常允许将NULL
传递给函数期望const char *
参数,但是将NULL
传递给strlen()
)是未定义的行为。
我说“技术上”,因为我认为,鉴于标准中的其他限制,实施需要故意反常导致此案件出现问题。
答案 1 :(得分:8)
不,因为%x格式化unsigned int,并且常量表达式1的类型是int,而它的值可表示为unsigned int。操作不是UB。
答案 2 :(得分:3)
这是未定义的行为,原因与将整数类型的指针重新解释为相反签名的互补类型的原因相同。不幸的是,这在两个方向都是不允许的,因为一个中的有效表示可能是另一个中的陷阱实现。
我看到从签名到无符号重新解释的唯一原因可能是陷阱表示是这种变换的符号表示情况,其中无符号类型只掩盖了符号位。不幸的是,从标准的6.2.6.2开始就允许这样的事情。 在这样的体系结构中,带符号类型的所有负值都可以是无符号类型的陷阱表示。
在您的示例中,这更加奇怪,因为不允许使用1
无符号类型的陷阱表示。因此,要使其成为“真实”的示例,您必须使用-1
提出问题。
我认为仍然没有任何架构可供人们编写具有这些功能的C编译器,因此如果更新版本的标准可以废除这个令人讨厌的案例,那么明确的生活会变得更加容易。
答案 3 :(得分:0)
我相信这是不确定的。具有可变长度参数列表的函数在接受参数时没有隐式转换,因此1
在过去unsigned int
时不会强制转换为printf()
,从而导致未定义的行为。
答案 4 :(得分:0)
TL; DR不是UB。
为n。代词answer中指出,C标准指出,带符号整数类型的所有非负值都具有与对应的无符号类型完全相同的表示形式,因此只要该值位于两种类型的范围。
根据C99标准6.2.5类型-第9段和脚注31:
9有符号整数类型的非负值范围是一个子范围 对应的无符号整数类型,以及 每种类型中相同的值是相同的。 31)
31)相同的表示形式和对齐要求旨在 暗示可互换性作为函数的参数,从中返回值 职能和工会成员。
与C11标准中的6.2.5类型完全相同的文本-第9段和脚注41。
答案 5 :(得分:-1)
标准的作者通常不会尝试在每个可以想象的极端情况下明确强制行为,特别是当存在明显正确的行为时,100%的所有实现都会共享,并且没有理由期望任何实现还要别的吗。尽管标准明确要求有符号和无符号类型具有适合两者的值的匹配内存表示,但理论上实现可能会以不同方式将它们传递给可变参数函数。标准并未禁止此类行为,但我没有看到作者故意允许的证据。最有可能的是,他们根本没有考虑这种可能性,因为从来没有实施过(而且据我所知,曾经有过)。
如果代码在签名值上使用%x,那么清理实现可能是合理的,尽管质量清理实现还应提供静默接受此类代码的选项。理智的实现除了将传递的值处理为unsigned之外,或者如果它在诊断/消毒模式中使用时,都没有理由做任何事情。虽然标准可能会禁止某个实现将任何在签名值上使用%x的代码视为无法访问,但任何认为实现应该利用这种自由的人都应该被认为是白痴。
专门针对理智的非诊断实现的程序员在输出诸如“uint8_t”值之类的东西时不需要担心添加强制转换,但那些代码可能被提供给moronic实现的程序员可能想要添加这样的强制转换以防止编译器从“优化”这样的实现可能会强加。