很容易理解这样的代码是如何工作的:
#include <string.h>
#define strcmp my_strcmp
int my_strcmp(const char *, const char *)
...
strcmp(str1, str2);
...
但问题是这在技术上是否正确。
从C11开始:
7.1.3.1(关于保留名称):
...
- 以下任何子条款中的每个宏名称(包括将来的库) 如果包含任何相关标题,则保留指定用途; 除非另有明确说明(见7.1.4)。
- 以下任何子条款中包含外部链接的所有标识符(包括 未来的图书馆方向)和errno总是保留用作标识符 外部联系。 184 )
- 具有文件范围的每个标识符在以下任何子条款中列出(包括 未来的图书馆方向)保留用作宏名称和标识符 如果包含任何相关标头,则文件范围在同一名称空间中。
184 具有外部链接的保留标识符列表包括math_errhandling,setjmp,va_copy和va_end。
因此,这意味着strcmp
是保留字,因为包含string.h
。
7.1.3.2:
...如果程序在保留它的上下文中声明或定义标识符(除了7.1.4允许的标识符),或者将保留标识符定义为宏名称,则行为是未定义的。
现在这似乎说重新定义strcmp
是未定义的行为,除了它在7.1.4中以某种方式允许。
7.1.4可能相关的内容是:
7.1.4.1:
...标头中声明的任何函数可以另外实现为标头中定义的类函数宏,因此如果在包含标头时显式声明了库函数,则可以使用下面显示的技术之一确保声明不受此类宏观影响。通过将函数的名称括在括号中,可以在本地抑制函数的任何宏定义,因为该名称后面没有左括号,后面表示宏函数名称的扩展。出于同样的语法原因,允许获取库函数的地址,即使它也被定义为宏。 185 )使用#undef删除任何宏定义也将确保参考实际功能。 ...
185 这意味着实现应为每个库函数提供实际函数,即使它还为该函数提供宏。
7.1.4.2:
如果可以在不引用标题中定义的任何类型的情况下声明库函数,则允许声明该函数并使用它而不包括它 相关标题。
其余条款无关紧要。我没有看到7.1.3和#34;所引用的内容如7.1.4&#34;所允许的那样,除了与函数在同一标题中的库函数的定义,即标准头,作为宏。
总之,上面的代码是技术上未定义的行为吗?如果不包括string.h
怎么样?
答案 0 :(得分:8)
UB的至少一个原因是string.h
可以引入宏。出于内部实现的原因,这些宏可能是在假设strcmp
是“真正的”strcmp函数的情况下编写的。如果您将strcmp
定义为其他内容,然后使用这些宏,strcmp
将在宏中扩展为my_strcmp
,并产生意外后果。
而不是试图确定...
中哪些代码可以正常运行,哪些代码没有,标准会尽早停止你的恶作剧。
另请注意,除了标准限制禁止它之外,您的#define strcmp my_strcmp
可能是宏重新定义,因为string.h
被允许执行#define strcmp __strcmp
或其他。因此,在一些符合要求的实现中,您的代码是不正确的。
答案 1 :(得分:5)
宣布或定义保留标识符的程序并不严格符合(C 2011 4 5),但可能符合(C 2011 4 7)。
dispute out of which this question arose不是关于声明或定义保留标识符是否是C未定义的行为,而是行为是否可以通过其他方式定义,例如特定C实现的文档,以及是否程序作者可以做到。
有些人将“未定义的行为”视为“您可能不会这样做。”这是对“未定义行为”的错误解释。未定义的行为不是标准要求您避免的行为;这是C标准无法帮助你的东西。
C标准明确声明它对未定义的行为强加无要求。特别是,这意味着不要求您不要这样做,也不要求其他规范不能定义行为。几乎每个实际程序都使用C标准没有定义的行为,当它通过库文档定义的操作系统文档或库调用定义系统调用时,或者依赖于由特定C实现定义的数据类型的格式。对
在C中,“未定义的行为”仅仅是C标准设定的规则的结束。这是一个开放的领域,您可以使用其他方式导航,而不是阻碍您前进的墙。
答案 2 :(得分:0)
是的,这是未定义的行为。可能它会起作用,但你无法确定。
我认为这样做的原因是允许编译器内置有关str
函数如何工作的知识。当然,strcmp
可能已经是memcmp(s1, s2, strlen(s1))
的宏观或类似的东西[我不是说它是怎么回事,只是可能。
我不相信“不包括string.h”实际上有帮助。
我建议您在源代码中搜索并替换strcmp
和my_strcmp
。如果你想“回去”,你总是可以反过来使用#define my_strcmp(s1, s2) strcmp(s1, s2)
。