从标准库重新定义功能

时间:2018-06-07 12:52:46

标签: c gcc compilation clang

上下文:在最近的一次对话中,问题" gcc / clang在编译时做strlen("static string")?"来了经过一些测试,答案似乎是肯定的,无论优化程度如何。甚至在-O0看到这一点我感到有些惊讶,所以我做了一些测试,最终得到了以下代码:

#include <stdio.h>

unsigned long strlen(const char* s) {
  return 10;
}

unsigned long f() {
  return strlen("abcd");
}

unsigned long g(const char* s) {
  return strlen(s);
}

int main() {
  printf("%ld %ld\n",f(),g("abcd"));
  return 0;
}

令我惊讶的是,它打印4 10而非10 10。我尝试使用gccclang以及各种标记(-pedantic-O0-O3-std=c89-std=c11进行编译,...)并且测试之间的行为是一致的。

由于我没有包含string.h,因此我预计会使用strlen的定义。但汇编代码确实显示strlen("abcd")基本上被return 4取代(这是我在运行程序时观察到的)。

此外,编译器不会使用-Wall -Wextra打印任何警告(更准确地说,没有与此问题相关的警告:他们仍然警告我在s的定义中未使用参数strlen。< / p>

出现了两个(相关的)问题(我认为它们足够相关,可以在同一个问题中提出):
  - 当标题声明不包括在内时,是否允许在C中重新定义标准函数?
  - 这个程序的行为应该如何吗?如果是这样,究竟会发生什么?

1 个答案:

答案 0 :(得分:6)

Per C 2011(草案N1570)7.1.3 1和2:

  

以下任何子条款中具有外部链接的所有标识符...始终保留用作具有外部链接的标识符。

     

如果程序在保留它的上下文中声明或定义标识符(除了7.1.4允许的标识符),或者将保留标识符定义为宏名称,则行为未定义。

“以下子条款”指定标准C库,包括strlen。您的程序定义strlen,因此其行为未定义。

您观察到的情况是:

  • 编译器知道strlen应该如何表现,无论您的定义如何,因此,在strlen("abcd")中优化f时,它会在编译时评估strlen,结果四分之一。
  • g("abcd")中,编译器无法识别,由于g的定义,这等同于strlen("abcd"),因此它不会在编译时对其进行优化。相反,它会将其编译为对g的调用,并编译g以调用strlen,它还会编译您对strlen的定义,结果为g("abcd") 1}}调用g,调用您的strlen,返回十。

C标准允许编译器完全放弃strlen的定义,以便g返回4。但是,一个好的编译器应该警告你的程序定义了一个保留的标识符。