【问题标题】:Is it undefined behavior to redefine a standard name?重新定义标准名称是未定义的行为吗?
【发布时间】:2013-02-08 10:23:05
【问题描述】:

很容易推断这样的代码是如何工作的:

#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.2 所指的“7.1.4 所允许的”,除了库函数的定义在与函数相同的头文件中,即标准头文件,作为宏。

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

【问题讨论】:

  • 这个问题是基于一个错误的前提。 here 引起的争议不是定义保留标识符是否由 C 未定义,而是它是否可以通过其他方式定义以及一个人是否可以这样做(即,在某些情况下,一个人是否可以进行生产性使用C 标准未定义的 C 实现的行为)。

标签: c language-lawyer


【解决方案1】:

它是 UB 的至少一个原因是 string.h 可以引入宏。出于内部实现的原因,这些宏可能是在假设strcmp 是“真正的”strcmp 函数的情况下编写的。如果您将strcmp 定义为其他内容,然后使用这些宏,则strcmp 将在宏中扩展为my_strcmp,从而产生意想不到的后果。

与其尝试确切地确定 ... 中哪些代码可以使用,哪些代码不行,该标准会尽早制止您的恶作剧。

还请注意,除了标准禁止它之外,您的 #define strcmp my_strcmp 可能是一个宏重新定义,因为 string.h 被允许执行 #define strcmp __strcmp 或其他任何事情。因此,在某些符合要求的实现中,您的代码格式不正确。

【讨论】:

  • 我也是这么想的。如果string.h包含呢?
  • @Shahbaz: strcmp 有点特殊,因为它被为未来方向保留的名称所涵盖(任何以str 开头的名称),所以我认为如果您包含 任何标准标题。但是,通常,在 C 中,如果您不包含标准标头,则它定义的名称不会保留为宏/标识符,但它们仍然全部保留用于外部链接。标准称有些名称由多个标题定义。
  • 对。感谢您的回答。我曾在an answer of mine 中说过这是未定义的行为,但对其他人来说似乎并不清楚。我要求确定。
  • @Shahbaz:Eric 是正确的,因为某些东西是 UB 并不意味着“你做不到”。这意味着该标准并未赋予您的程序不可剥夺的权利。他并不是说这不是未定义的行为,而是说你不能这样做不一定是真的。如果您正在编写可移植的代码,这几乎是相同的事情,您不会想要做任何只有某些实现保证才能工作的事情。如果您正在编写不可移植的代码,那么您的系统文档与标准一样有效。
【解决方案2】:

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

dispute out of which this question arose 不是关于声明或定义保留标识符是否是 C 未定义的行为,而是该行为是否可以通过其他方式定义,例如特定 C 实现的文档,以及程序是否作者可以的。

有些人将“未定义的行为”视为“您可能不会这样做”。这是对“未定义行为”的错误解释。未定义的行为不是标准要求您避免的;这是 C 标准无法帮助您解决的问题。

C 标准明确声明它对未定义的行为施加无要求。特别是,这意味着没有要求你不能这样做,也没有要求另一个规范不能定义行为。几乎每个实际程序都使用 C 标准未定义的行为,当它进行操作系统文档定义的系统调用或库文档定义的库调用或依赖于其设计的特定 C 实现定义的数据类型格式时为。

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

【讨论】:

  • C 标准也没有判断用于各种目的的质量实现是否应该定义超出标准规定的各种行为,也没有判断实现是否适用于任何特定目的[或任何目的]而不这样做。例如,一个低质量但符合标准的实现可能会对字母“i”在源文本中出现的次数施加限制,并且如果程序超出该限制,则其行为基本上是任意的。一个非常低质量的实现可以将该限制设置为低至 0...
  • ...如果超出该限制时它的行为方式与某些特定人为程序的行为一致,该程序行使标准的翻译限制部分中的所有限制。毕竟,将后一个“一个程序”输入到实现中会产生与以一致方式“处理”它没有区别的行为。当然,高质量的实现不应该那样做,但重点是标准并没有试图禁止低质量的实现可能使自己无用(或不如其他方式有用)的所有方式。
【解决方案3】:

是的,这是未定义的行为。它很可能会起作用,但你不能确定。

我相信这样做的原因是为了让编译器对str 函数的工作原理有内置知识。当然,strcmp 可能已经是memcmp(s1, s2, strlen(s1)) 的宏或类似的东西[我不是说它就是这样,只是可能是这样。

我不相信“不包括 string.h”实际上有帮助。

我建议您在源代码中搜索并替换 strcmpmy_strcmp。如果您想“返回”,您总是可以反过来使用#define my_strcmp(s1, s2) strcmp(s1, s2)

【讨论】:

  • 这不是我的代码。我answered 提出另一个问题,即这是未定义的行为,我遇到了阻力。这就是为什么要求确保。
  • 好的,所以您可能想建议“反其道而行之”!
猜你喜欢
  • 2019-09-29
  • 2023-03-31
  • 2020-09-03
  • 2023-02-07
  • 1970-01-01
  • 2022-08-22
  • 2013-02-18
  • 1970-01-01
  • 2022-06-10
相关资源
最近更新 更多