linux c 宏定义 #define _GNU_SOURCE 含义

杜河
2023-12-01

今天我必须要使用 basename 函数,使用man手册查询了一下这个函数 man 3 basename 发现一个奇怪的信息

名称

basename,dirname-分析路径名组件

简介

#include <libgen.h>
char *dirname(char *path);
char *basename(char *path);

警告
有两个不同的函数basename()-请参阅下面的。

函数dirname()和basename()将以空结尾的路径名字符串分解为目录和文件名组件。在通常情况下,dirname()返回最后一个“/”之前(但不包括在内)的字符串,basename()返回最后一个“/”之后的组件。尾随的“/”字符不算作路径名的一部分。

如果路径不包含斜杠,dirname()将返回字符串“.”,而basename()将返回路径的副本。如果path是字符串“/”,则dirname()和basename()都会返回字符串“/”。如果path是空指针或指向空字符串,则dirname()和basename()都返回字符串“.”。

将dirname()返回的字符串、一个“/” 和basename()返回的字符串连接起来,将生成完整的路径名.
dirname()和basename()都可能会修改路径的内容,因此在调用这两个函数时传入参数一定是一个路径的副本。

这些函数可能返回指向静态分配内存的指针,这些指针可能会被后续调用覆盖。或者,它们可以返回指向路径某一部分的指针,以便在不再需要函数返回的指针之前,不应修改或释放由路径引用的字符串。

下面的列表(取自SUS V2)显示了dirname()和basename()为不同路径返回的字符串:

pathdirnamebasename
/usr/lib/usrlib
/usr//usr
usr.usr
///
...
.

返回值

dirname()和basename()都返回指向以空结尾的字符串的指针。(不要将这些指针传递给free(3)。)

ATTRIBUTES

   For an explanation of the terms used in this section, see attributes(7).

   ┌──────────────────────┬───────────────┬─────────┐
   │Interface             │ Attribute     │ Value   │
   ├──────────────────────┼───────────────┼─────────┤
   │basename(), dirname() │ Thread safety │ MT-Safe │
   └──────────────────────┴───────────────┴─────────┘

注意

basename()有两个不同的版本————上面描述是posix版本,gnu版本是下面:

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <string.h>

GNU版本从不修改它的参数,当路径的结尾是一个斜杠时返回空字符串,特别是当它是“/”时。没有dirname()的GNU版本。
使用glibc,当包含<libgen.h>时,将获得basename()的POSIX版本,否则将获得GNU版本。

漏洞

在glibc实现中,这些函数的posix版本修改了path参数,并在使用诸如“/usr/”之类的静态字符串调用时修改产生段错误。

在glibc 2.2.1之前,dirname()的glibc版本没有正确地处理带有尾随“/”字符的路径名,如果给定了空参数,则会生成段错误。

例子

#include <libgen.h>
#include <stdio.h>
#define _GNU_SOURCE 
#include <string.h>
int main(){
    char *dirc, *basec, *bname, *dname;
    char *path = "/etc/passwd";
    dirc = strdup(path);
    basec = strdup(path);
    dname = dirname(dirc);
    bname = basename(basec);
    printf("dirname=%s, basename=%s\n", dname, bname);
  return 0;
}

两个问题:

  1. 如果是这样,为什么不给人们提供不同的头文件,没有必要定义一些模糊的宏来获取函数的实现或者其他啊?
  2. 还有一些问题也让我抓狂:编译器怎么知道要连接那个函数到可执行文件? 也是使用 #define 的方式吗?

解答一

定义 _GNU_SOURCE 与许可证无关,与编写(非)可移植代码无关。如果您定义了“_GNU_SOURCE ”,您将得到:

  • 访问许多非标准的GNU/Linux扩展函数
  • 对POSIX标准中省略的传统功能的访问(通常是有充分理由的,例如被更好的替代方案替换,或者与特定的遗留实现绑定)
  • 访问不可移植的低级功能,但有时需要实现系统实用程序,如mount、ifconfig等。

对于许多POSIX指定的函数来说,行为是不正常的,GNU人员不同意标准委员会关于函数应该如何运行,并决定自己做一些事情。

只要你知道这些事情,定义 _GNU_SOURCE 就不是问题了,但是你应该避免定义它,而是在可能的时候定义POSIX_C_source=200809L或Xopen_source=700,以确保你的程序是可移植的。

尤其是,你不应该使用的来自GNU来源的东西是上面的2和4。

解答二

如果是这样,为什么不给人们提供不同的头文件,没有必要定义一些模糊的宏来获取函数的实现或者其他啊?

在不同的Unix版本中,同一个头部的内容通常稍有不同,因此没有单一的正确内容,例如,<string.h>—有许多标准(xkcd)。在一整套宏中选择你最想要的,这样,这个库可以满足不同人的标准。

还有一些问题也让我抓狂:编译器怎么知道要连接那个函数到可执行文件? 也是使用 #define 的方式吗?

一种常见的方法是根据 _GNU_SOURCE 是否被定义来有条件的 给标识符 basename 定义不同的名称:

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

这样库的实现中只要提供两种实现即可。

解答三

来自google的邮件列表

看看 glibc’s include/features.h:

定义了_GNU_SOURCE 表示可以使用
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 包括 GUN 扩展内容。

因此,它为gcc启用了大量编译标志

解答四

想详细了解 _GNU_SOURCE ,可以看一下下面的文档。

来自 GUN 文档

宏:_GNU_SOURCE

如果您定义这个宏,所有内容都包括在内: ISO C89, ISO C99, POSIX.1, POSIX.2, BSD, SVID, X/Open, LFS和GUN扩展。在POSIX.1与BSD冲突的情况下,POSIX.1 定义优先。

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(600在2.10之前的glibc版本中;在2.2之前的glibc版本中为500)。此外,还公开了各种特定于GNU的扩展。

自从glibc 2.19以来,定义_GNU_SOURCE也具有隐式定义_DEFAULT_SOURCE的效果。在2.20之前的glibc版本中,定义_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,而是以这种方式定义的任何宏)。

 类似资料: