“#define _GNU_SOURCE” 表示什么?

204
今天我需要使用basename()函数,但是man 3 basename(这里)给了我一些奇怪的信息:

说明

basename()函数有两个不同的版本 - 上面描述的POSIX版本和GNU版本,在加入以下代码后可以使用GNU版本:

#define _GNU_SOURCE
#include <string.h>

我想知道这个#define _GNU_SOURCE是什么意思:它会用GNU相关许可证来污染我编写的代码吗?还是仅仅用于告诉编译器像“嗯,我知道,这个函数集不是POSIX标准的,因此不具备可移植性,但我仍然想使用它”。

如果是这样,为什么不给人们不同的头文件,而是必须定义一些晦涩的宏来获取一个函数实现或另一个函数实现呢?

还有一些问题困扰着我:编译器如何知道要链接哪个函数实现到可执行文件中?它也使用这个#define吗?

有人能给我一些指示吗?

5个回答

227
定义_GNU_SOURCE与许可证无关,但与编写(不)可移植代码有关。如果您定义了_GNU_SOURCE,则会获得以下内容:
  1. 访问大量非标准GNU / Linux扩展函数
  2. 访问传统函数,这些函数被省略了POSIX标准(通常有很好的理由,例如用更好的替代品替换或与特定的遗留实现绑定)
  3. 访问某些无法移植但有时需要用于实现系统实用程序如mountifconfig等的低级函数
  4. 对许多已规定POSIX功能的错误行为,在那些GNU的开发人员因为不同意标准委员会关于函数应该如何运行而决定采取自己的方式。
只要您已经意识到这些事情,定义_GNU_SOURCE就不应该是问题,但是在可能的情况下应避免定义它,并使用_POSIX_C_SOURCE = 200809L_XOPEN_SOURCE = 700来确保您的程序具有可移植性。
特别地,您永远不应该使用_GNU_SOURCE中的#2和#4。

94
当然,每个人都知道定义_GNU_SOURCE的真正原因是为了使用strfrymemfrob - user4815162342
8
这个链接是GNU C库文档,提供了一些额外的细节(例如,建议在文件中“#define _GNU_SOURCE”应该是“注释之后的第一件事,仅在注释之前有”。) - Alexander Pozdneev
1
@mckenzm:我想你在想_FILE_OFFSET_BITS,而不是_GNU_SOURCE - R.. GitHub STOP HELPING ICE
2
我想成为一名有薪的程序员,为将strfrymemfrob和类似的工具移植到其他平台和工具链中做出贡献。 - Massimo
2
@user4815162342:此链接已失效。 - InQusitive
显示剩余4条评论

22

关于 _GNU_SOURCE 启用了哪些功能的确切细节,可以查阅文档。

来自 GNU 文档:

宏:_GNU_SOURCE

如果您定义了此宏,则会包括以下所有内容:ISO C89、 ISO C99、POSIX.1、 POSIX.2、BSD、SVID、X/Open、LFS 和 GNU 扩展。 在 POSIX.1 与 BSD 冲突的情况下,POSIX 定义优先。

来自 Linux 上有关特性测试宏 的 man 页面:

_GNU_SOURCE

定义此宏(带任何值)会隐式地定义 _ATFILE_SOURCE、_LARGEFILE64_SOURCE、_ISOC99_SOURCE、_XOPEN_SOURCE_EXTENDED、_POSIX_SOURCE、_POSIX_C_SOURCE,并将其值设置为 200809L (在 glibc 2.10 之前的版本中为 200112L;在 glibc 2.5 之前的版本中为 199506L;在 glibc 2.1 之前的版本中为 199309L),以及将 _XOPEN_SOURCE 的值设置为 700(在 glibc 2.10 之前的版本中为 600;在 glibc 2.2 之前的版本中为 500)。此外,还公开了各种 GNU 特定扩展功能。

自 glibc 2.19 以来,定义 _GNU_SOURCE 还有隐式定义 _DEFAULT_SOURCE 的效果。在 glibc 2.20 之前的版本中,定义 _GNU_SOURCE 还会隐式定义 _BSD_SOURCE 和 _SVID_SOURCE。

注意:需要在包含头文件之前定义 _GNU_SOURCE,以便相应的头文件启用这些功能,例如:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
...

使用-D标志,还可以在编译时启用_GNU_SOURCE

$ gcc -D_GNU_SOURCE file.c

(-D不仅适用于_GNU_SOURCE,而是任何以此方式定义的宏)。


7

让我回答另外两个问题:

还有一件事情困扰着我:编译器如何知道与可执行文件链接哪个函数实现?它也使用 #define 吗?

常见的方法是根据是否定义了 _GNU_SOURCE,有条件地将标识符 basename 定义为不同的名称。例如:

#ifdef _GNU_SOURCE
# define basename __basename_gnu
#else
# define basename __basename_nongnu
#endif

现在,该库只需要提供这些名称下的两种行为即可。
如果是这样,为什么不给人们不同的头文件,而不是不得不定义一些晦涩的环境变量来获得一个函数实现或另一个函数实现呢?
通常,相同的头文件在不同的Unix版本中具有稍微不同的内容,因此对于例如 <string.h> 这样的头文件,没有单一正确的内容 — 存在许多标准 (xkcd)。有一整套宏可以选择您喜欢的标准,以便如果您的程序期望某个标准,则库将符合该标准。

6

来自某个邮件列表通过 Google 翻译:

查看 glibc 的 include/features.h:

_GNU_SOURCE 所有上述内容,加上 GNU 扩展。

这意味着它启用了所有这些内容:

STRICT_ANSI,_ISOC99_SOURCE,_POSIX_SOURCE,_POSIX_C_SOURCE, _XOPEN_SOURCE,_XOPEN_SOURCE_EXTENDED,_LARGEFILE_SOURCE, _LARGEFILE64_SOURCE,_FILE_OFFSET_BITS=N,_BSD_SOURCE,_SVID_SOURCE

因此,它为 gcc 启用了许多编译标志。


21
这不会影响编译器的行为,只会影响从头文件中可见的原型和其他内容。 - Spudd86

2

为什么不给人们不同的标题呢?

他们已经有了它们;标题被按主题分成文件,所以需要另一个维度来过滤。

我正在寻找信号数字到名称的转换。我在<string.h>中找到了strsignal()。手册上说:

sigabbrev_np(), sigdescr_np():
       _GNU_SOURCE                              <<< not default
strsignal():
       From glibc 2.10 to 2.31:
           _POSIX_C_SOURCE >= 200809L           <<< default, cf. XOPEN2K8 below
       Before glibc 2.10:
           _GNU_SOURCE

我从来没有真正关心过这一部分。 sigabbrev_np() 不包含在默认的“特性”中。 string.h 显示如何:
#ifdef  __USE_XOPEN2K8
/* Return a string describing the meaning of the signal number in SIG.  */
extern char *strsignal (int __sig) __THROW;

# ifdef __USE_GNU
/* Return an abbreviation string for the signal number SIG.  */
extern const char *sigabbrev_np (int __sig) __THROW;
/* Return a string describing the meaning of the signal number in SIG,
   the result is not translated.  */
extern const char *sigdescr_np (int __sig) __THROW;
# endif

__USE_GNU 可以/应该通过编译或文件顶部的 _GNU_SOURCE 设置。但是这也会“激活”所有其他头文件中的 ifdeffed 声明。(除非您按头文件定义-取消定义)

因此,为了明确地导入仅一个(或另一个)特殊函数,我现在这样做(复制粘贴。我留下了 "THROW" 并更改了 "__sig"):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
extern const char *sigabbrev_np(int sig) __THROW;  /* __USE_GNU / _GNU_SOURCE */
#include <errno.h>
#include <elf.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
...

现在使用sigabbrev_np(wstate >> 8)可以给我提供TRAP等内容,而无需使用#define。

我曾经花费很长时间才意识到0x57f表示OK,因为5TRAP,但0xb7f0x77f分别是SEGVBUS --- 这取决于我设置断点的位置,有时需要执行数千条指令。因为我没有回退指令指针...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接