【问题标题】:Is it possible to detect conflicting use of reserved identifiers in C?是否可以检测 C 中保留标识符的冲突使用?
【发布时间】:2018-10-26 15:09:28
【问题描述】:

根据 C 标准,如果程序定义或声明了保留标识符,则行为未定义。一类保留标识符是在 C 标准库中定义的具有外部链接的标识符。

对于具有未定义行为的程序,请考虑以下情况:file1.c 定义了一个名为 time 的具有外部链接的变量,它与 time.h 中声明的标准库中的 time 函数冲突。

file1.c:

int time;

int foo( void )
{
    return time;
}

file2.c:

#include <time.h>
#include <stdio.h>

extern int foo( void );

int main( void )
{
    foo();
    printf( "current time = %ld\n", time( NULL ) );
    return 0;
}

当程序编译并运行时,会发生段错误,因为 file2.c 中引用的time 符号链接到 file1.c 中的time 变量,而不是 C 库中的函数。

$ gcc -c -o file1.o file1.c
$ gcc -c -o file2.o file2.c
$ gcc -o test file1.o file2.o 
$ ./test
Segmentation fault (core dumped)

我想知道 GCC 是否有任何方法可以在编译或链接时检测用户代码中冲突的保留标识符的使用。这是我的动机:我正在开发一个应用程序,用户可以在其中为应用程序编写 C 扩展,这些扩展被编译并链接到应用程序的其余部分。如果用户的 C 代码像上面的示例一样使用保留标识符,则生成的程序可能会以难以预测的方式失败。

想到的一个解决方案是在用户的目标文件上运行 nm 之类的东西,并将定义的符号与 C 库中的保留标识符列表进行比较。但是,我希望在 GCC 中找到可以检测到问题的东西。有谁知道这是否可行,或者有什么建议?

【问题讨论】:

  • 好问题。事实上,GCC 能够警告许多标识符,特别是隐式声明不符合标准,但不是关于这个。
  • 您可以从禁止全局变量开始。 gcc 应该能够在同一个翻译单元中检测与标准库的冲突。
  • 或制作一个简单的解析器,检查用户导出的符号是否遵循可以禁用任何阴影的简单语法。例如。强制所有用户的全局符号以usr_ 或其他东西为前缀
  • 这并不是编译器可以给出错误的任何东西,因为它只处理translation units。链接器可能能够做到这一点,但是对于 C 来说,符号只是一个符号,实际上没有关于符号的语义信息可供链接器检测到。
  • 另一种选择:将所有代码编写为库代码,将所有导出的标识符正确命名为命名空间前缀,问题不再存在。

标签: c gcc


【解决方案1】:

您可以获取一个 libc 实现,您可以静态链接并使用 -Wl,--whole-archive 并尝试将其添加到您的目标文件中。

main.c:

int time=42;
int main(){}

将它与整个 libc 链接:

$ musl-gcc main.c -static -Wl,--whole-archive

如果您收到多重定义错误或符号类型/大小/对齐方式更改警告,则说明您与 libc 发生冲突。

/usr/local/bin/ld: /usr/local/musl/lib/libc.a(time.lo): in function `time':
/home/petr/f/proj/bxdeps/musl/src/time/time.c:5: multiple definition of `time'; /tmp/cc3bL3pP.o:(.data+0x0): first defined here

或者(更强大)您可以预先包含和 all-of-C (all-of-posix) 标头,并让编译器告诉您与它发生冲突的位置(我只做一次)同时,否则它会在某种程度上使您的构建时间变得悲观。(尽管即使包括所有 POSIX 通常也不像包括单个 C++ 标头那么糟糕)。

【讨论】:

  • 1) 我有时也会发现包括所有#includes 的信息。 2) 今天学了一个新词:pessimize.
  • @chux 有一段时间我预先包含了一个预编译的whole-of-c-and-posix 头文件,甚至没有考虑选择性地包含系统内容。与甚至“便宜”的 C++ 包含的成本相比(例如,在我的 PC 上处理 大约需要 300 毫秒),all-of-posix/all-of-c 相当便宜(在我的 PC 上约为 40 毫秒)。
【解决方案2】:

我想知道 GCC 是否有任何方法可以在编译或链接时检测用户代码中冲突的保留标识符的使用情况。

详细信息@PSkocik 好答案。
检测许多冲突的一种方法是包含所有头文件。编译时间可能会明显增加。

Determine version

#if defined(__STDC__)
# define STANDARD_C89
# if defined(__STDC_VERSION__)
#  define STANDARD_C90
#  if (__STDC_VERSION__ >= 199409L)
#   define STANDARD_C95
#  endif
#  if (__STDC_VERSION__ >= 199901L)
#   define STANDARD_C99
#  endif
#  if (__STDC_VERSION__ >= 201112L)
#   define STANDARD_C11
#  endif
#  if (__STDC_VERSION__ >= 201710L)
#   define STANDARD_C18
#  endif
# endif
#endif

包括他们,有些是有选择的。

#include <assert.h>
//#include <complex.h>
#include <ctype.h>
#include <errno.h>
//#include <fenv.h>
#include <float.h>
//#include <inttypes.h>
//#include <iso646.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
//#include <stdalign.h>
//#include <stdatomic.h>
//#include <stdbool.h>
#include <stddef.h>
//#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
//#include <stdnoreturn.h>
#include <string.h>
//#include <tgmath.h>
//#include <threads.h>
#include <time.h>
//#include <uchar.h>
//#include <wchar.h>
//#include <wctype.h>

//////////////////////////////
#ifdef STANDARD_C95
#include <iso646.h>
#include <wchar.h>
#include <wctype.h>
#endif

//////////////////////////////
#ifdef STANDARD_C99
#ifndef __STDC_NO_COMPLEX__
#include <complex.h>
#endif
#include <fenv.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <tgmath.h>
#endif

//////////////////////////////
#ifdef STANDARD_C11
#include <stdalign.h>
#ifndef __STDC_NO_THREADS__
#include <stdatomic.h>
#include <threads.h>
#endif
#include <stdnoreturn.h>
#include <uchar.h>
#endif

我确信上述内容需要一些改进,并希望就此提出建议。


avoid additions 到命名空间,而不是像#define STANDARD_C11 这样的代码,使用宏代码测试

// #ifdef STANDARD_C11
//  ... C11 includes
// #endif

#if defined(__STDC__)
# if defined(__STDC_VERSION__)
#  if (__STDC_VERSION__ >= 201112L)
     ... C11 includes
#  endif
# endif
#endif

虽然目标是“根据 C 标准......”,但可能需要额外的代码来适应流行的编译器扩展和标准的细微变化。

【讨论】:

  • 我会删除 STANDARD_* 宏并将它们替换为针对 STDC 的内联测试(以免不必要地污染命名空间)。为了真正实现最大的可移植性,最好将它与配置测试系统(如 autotools)结合起来,并额外保护每个包含(例如,tinycc 没有 iso646.h)。 (如果你想包含所有的 posix 并让它在 Linux/Cygwin/Mac 和各种编译器上工作,那就更重要了)。
  • @PSkocik 嗯,如果 TinyCC 使用 199901L 却没有 iso646.h,是否合规?
  • 不是 100%。 (所以也许我不应该关心它,但我关心它,因为我喜欢速度 :D))。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-15
  • 2012-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多