标准库函数定义为宏,传入错误的参数类型

12

这里是示例代码:

#include <ctype.h>

int main(void)
{
    isalpha("X");
}

我的问题是:这段代码是否违反了限制条件?同样地,如果一个实现没有发出诊断,它是否不符合规范?


动机:多个主要编译器甚至在符合规范的代码中也不会对此代码发出警告。C11 6.5.2.2/2 表明将char *传递给预期为int的函数的原型是一种限制条件违规。

然而,我不清楚7.1.4中允许库函数被另外定义为宏的规定是否取代了6.5.2.2/2的要求。注脚187表明该宏隐藏了原型,但注脚并非正式文件。

当然,代码(isalpha)("X");会生成一条诊断信息。


这是否与此问题有关? - Some programmer dude
认真阅读后,我们可以在7.1.4中找到“出于相同的语法原因,即使已经将库函数定义为宏,也允许对其进行取地址。185)”,其中185表示“这意味着实现应该为每个库函数提供一个实际的函数,即使它还为该函数提供了一个宏。”。再次声明,脚注不是规范性文本。但我猜想,在那些不针对问题代码给出诊断消息的编译器上尝试int (*ptr)(int) = isalpha;编译可能会很有趣。 - Lundin
如果编译通过,那么如果你执行 int (*ptr)(int) = isalpha; ptr("X"); 会发生什么。 - Lundin
@Lundin 这个声明会让函数显然(类似函数的宏在没有参数列表时不会被替换)。 - M.M
如果你掌握了这个函数,你就能够检测到简单赋值违规的诊断能力。我的观点是,如果编译器仍然无法通过函数指针版本产生诊断信息,那么它肯定是有问题的,因为我们可以忽略7.1.4中的任何内容。我首先会进行这个测试,以检查某个编译器是否符合标准。 - Lundin
2个回答

2
我认为关键在于是否允许将isalpha定义为宏。 C11 7.1.4简要提到:
任何在头文件中声明的函数都可以作为函数宏在头文件中定义。
尽管本章主要涉及名称冲突和多线程问题等。另一方面,C11 7.4说:
头文件声明了几个有用于分类和映射字符的函数。
而C11 7.4.1.2则是:
int isalpha(int c); isalpha函数...
我认为isalpha应被视为函数。或者如果实现为宏,则必须由实现确保某种类型检查。
鉴于它是一个函数,从那里开始就很清楚了。对于所有的函数,函数调用的规则在C11 6.5.2.2中指定:
如果表示所调用函数的表达式具有包括原型的类型,则参数会隐式转换,就像通过赋值一样,将每个参数的类型取为其声明类型的未限定版本。
请注意“就像通过赋值一样”的部分。这导致我们遵循简单赋值的规则C11 6.5.16.1,约束。问题中的代码将在幕后等价于赋值表达式,例如int c =(char []){"X"};,其中左操作数是算术类型,右操作数是指针。在C11 6.5.16.1中找不到任何这样的情况。
因此,该代码是6.5.16.1的约束违规。
如果编译器库选择将isalpha实现为宏,并且由于未执行函数参数的正常lvalue转换而失去了类型检查能力,则如果编译器未能生成诊断消息,则该库可能非符合性。

请注意,“通常安全”的概念,例如宏不具有序列点... - Antti Haapala -- Слава Україні
@AnttiHaapala 有人已经想到了这一点。7.1.4/3“在库函数返回之前立即存在一个序列点。”使库函数成为特殊情况。 - Lundin
脚注说明了“通常安全”的意图是宏没有序列点。 - Antti Haapala -- Слава Україні
@AnttiHaapala 我想7.1.4/3只适用于实现为函数的情况。但与脚注不同,它是规范性文本。我能想到这段文字的唯一原因是,一些库可能确实将某些函数实现为宏。 - Lundin
顺便说一下,我猜脚注可能是关于参数评估后序列点,在函数调用之前。那是一个不同的序列点。 - Lundin

2
我的理解是,虽然标准要求存在一个名为isalpha的函数,在7.1.4中它特别允许实现定义一个同名的宏来隐藏函数声明。
这意味着在程序中调用isalpha(未经过#undef)允许将其宏展开为与6.5.2.2要求诊断的文字函数调用不同的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接