重新定义标准名称是不确定的行为?

Sha*_*baz 12 c language-lawyer

很容易理解这样的代码是如何工作的:

#include <string.h>

#define strcmp my_strcmp

int my_strcmp(const char *, const char *)

...
strcmp(str1, str2);
...
Run Code Online (Sandbox Code Playgroud)

但这个问题是这在技术上是否正确.

从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.2指的是"7.1.4允许的",除了与函数相同的头中的库函数的定义,即标准头,作为宏.

总之,上面的代码是技术上未定义的行为吗?如果string.h不包括在内怎么样?

Ste*_*sop 8

UB的至少一个原因是它string.h可以引入宏.出于内部实现的原因,这些宏可能strcmp是基于"真正的"strcmp函数的假设编写的.如果您定义strcmp为其他内容然后使用这些宏,strcmp则会扩展到my_strcmp宏中,并产生意外后果.

...该标准并没有试图确定哪些代码在什么样的代码中可以正常运行,而是什么都不行,而是为您的恶作剧提前做好准备.

另请注意,除了标准限制禁止它之外,您#define strcmp my_strcmp可能是宏重定义,因为string.h允许执行#define strcmp __strcmp或其他任何操作.因此,在一些符合要求的实现中,您的代码是错误的.


Eri*_*hil 5

声明或定义保留标识符的程序并不严格符合(C 2011 4 5),但可能符合(C 2011 4 7).

争端外面这个问题出现了不是关于是否声明或定义一个保留标识符是被C未定义但是否行为可以通过其他手段,例如用于特定的C实现文档来定义,和是否进行了行为程序作者可以做到.

有些人将"未定义的行为"视为"你可能不会这样做."这是对"未定义行为"的错误解释.未定义的行为不是标准要求你避免的; 它是C标准无法帮助你的东西.

C标准明确声明它对未定义的行为没有任何要求.特别是,这意味着不要求您不要这样做,也不要求其他规范不能定义行为.几乎每个实际程序都使用C标准没有定义的行为,当它通过库文档定义的操作系统文档或库调用定义系统调用时,或者依赖于由特定C实现定义的数据类型的格式.对于.

在C中,"未定义的行为"仅仅是C标准设定的规则的结束.这是一个开放的领域,您可以使用其他方式导航,而不是阻碍您进度的墙.