在C语言中,FILENAME_MAX和PATH_MAX有什么区别吗?

4
有时候我会被一些程序员所谓的文件名所困惑,而实际上那是我所说的路径,例如:/Users/example/Desktop/file.txt。我称之为文件名的只有file.txt,但显然有些人将/Users/example/Desktop/file.txt称为文件名,这让我很困惑。话虽如此,宏FILENAME_MAXPATH_MAX是否相同呢? FILENAME_MAXstdio.h或者其他地方定义,因此可以跨平台使用,但这样做是否会给自己带来额外的工作量呢?
#ifdef __linux__
#   include <linux/limits.h>
#elif defined(_WIN32)
#   include <windows.h>
#   define PATH_MAX MAX_PATH
#else
#   include <sys/syslimits.h>
#endif

只有当只使用#include <stdio.h>FILENAME_MAX 就足以创建足够大的路径缓冲区,以容纳任何文件路径时,才需要这样做。

https://www.gnu.org/software/libc/manual/html_node/Limits-for-Files.html - KamilCuk
1
使用注意:不要将FILENAME_MAX用作存储文件名的数组的大小!你不可能创建那么大的数组! - anthony sottile
但在 Mac 上只有 1024。 - user14629932
1
如果你要相信它是1024,那就不要使用常量,硬编码它。使用常量的目的是值可以不同。 - Schwern
1
为了实现可移植性,在创建文件时要保持在这些限制以下,但不要依赖于其他人创建的文件来尊重这些限制。 - HAL9000
4个回答

4

简而言之,不要相信他们中的任何一个。

"路径"是绝对路径,例如/home/you/foo.txt或相对路径,例如you/foo.txt或甚至foo.txt

"文件名"定义不清。它可能只是"基本名称"foo.txt,也可能是"path"的同义词。C标准似乎使用"文件名"来表示"path"。例如,fopen需要一个实际上是路径的filename

FILENAME_MAX是标准C,PATH_MAX是POSIX,MAX_PATH是Windows。

FILENAME_MAX是C标准常量...

它扩展为整数常量表达式,该表达式是足以容纳实现保证可以打开的最长文件名字符串的字符数组的大小。

PATH_MAX不是C标准的一部分。它是一个POSIX常量,表示路径名中包括终止空字符在内的最大字节数。

如果路径太长,POSIX函数应该会返回一个ENAMETOOLONG错误,但并非所有编译器都强制执行此规定。

MAX_PATH是一个Windows API常量...

在Windows API中(以下段落讨论的某些例外情况除外),路径的最大长度为MAX_PATH,定义为260个字符。本地路径按以下顺序组成:驱动器号、冒号、反斜杠、由反斜杠分隔的名称组件和终止空字符。例如,D驱动器上的最大路径为“D:\some 256-character path string”。

例如,C标准中的fopen使用FILENAME_MAX。POSIX open使用PATH_MAX
但是,不要完全相信它们。 FILENAME_MAX不能安全地用于分配内存,因为它可能是INT_MAX。 GCC文档警告...
PATH_MAX不同,即使没有实际限制,此宏也是定义的。在这种情况下,其值通常是一个非常大的数字。在GNU/Hurd系统上总是如此。
使用说明:不要将FILENAME_MAX用作存储文件名的数组的大小!您不可能创建那么大的数组!改用动态分配(请参见内存分配)。 PATH_MAX可能未定义。
因为它们是由操作系统硬编码的,所以两者都不能被信任为文件系统可以处理的真实表示。例如,我的 Mac 将 PATH_MAX 和 FILENAME_MAX 定义为 1024。FAT32 文件系统有一个 255 字符的限制。如果我在我的 Mac 上挂载 FAT32 文件系统,PATH_MAX 和 FILENAME_MAX 将是不正确的。 Evan Klitzke 描述了 PATH_MAX 的问题,并间接涉及了 FILENAME_MAX。
如果一个函数需要你分配一个缓冲区来存储路径,通常有一种替代方法会为你分配内存,或者会采取最大尺寸。如果你没有选择,1024 或 4096 是不错的选择。

1
如果在Mac上挂载msdos文件系统,您将能够创建超过255个字符的路径。问题是稍后,您将在dos文件系统中拥有无法从ms-dos打开的文件。 PATH_MAX限制是系统调用子系统对处理路径名的系统调用所施加的限制。它不依赖于文件系统(除非文件系统本身具有更严格的限制)。它只是为了保护内核内存免受滥用,因为内核需要复制所有文件路径以解析文件名。 - Luis Colorado

3

通常(很随意地)将PATH_MAX设为4096,而NAME_MAXFILENAME_MAX?)应为255,如此处所述

实际上,由于路径名长度、路径名连接和路径名扩展的不同方式(更不用说字符串编码的差异),一些文件名可能会比PATH_MAX/FILENAME_MAX更长,因此这种设置应被视为遗留问题。

此外,有时定义的PATH_MAX值与操作系统实际施加的限制(如果有的话)几乎没有关系。

长时间以来(也许仍然如此),Windows系统将PATH_MAX定义为260,而操作系统支持的路径名可达到32Kb或更多(此处有一些详细信息)。


4096并不是随意选择的。很可能,因为它是虚拟页面的长度,内核分配一个单独的页面来存储它,因此它具有更简单的结构来管理。页面是内核经常用于可能较大(但不太多)的结构的内存测量单位,并且如果内核需要空间进行更高性能的操作,则可以将其交换出去。 - Luis Colorado
@LuisColorado - 谢谢你。我理解这种有点武断的选择及其历史渊源的原因。但是现在可以说内核页面大小已经不重要了。此外,4096Kb页面大小可能已经过时(尽管我们非常依赖它,也许应该标准化)。 - Myst
1
是的...当然...openat(2)也有其作用。如果有人修改内核以允许任意长的文件名,那么恶意用户可以花费时间尝试打开名称为数十亿字节长的文件,并且内核可能需要期望具有此数量的不可交换内存来保存完整的文件名。简单地说,内核开发人员将解决所有问题。 - Luis Colorado

1
这两者根本无法进行比较。*nix常量PATH_MAX是*nix操作系统上可传递给系统调用的最大路径,但由于系统调用采用相对路径,因此可能存在更长的路径;而Windows常量MAX_PATH是Windows上可能出现的最长绝对路径。实际上,ABI已经撤销了MAX_PATH,但您需要自愿选择使用更长的路径。

@LuisColorado:关于MAX_PATH的内容来自Windows,而openat()在Windows中不存在。 - Joshua
Windows的最大路径名长度有限制...我所说的并不适用于Windows。你的答案没有做出任何假设,由于这个常量是在POSIX中定义的,恐怕Windows在这里不适用。使用openat(2)来访问更深层次的路径是没有必要的,可以使用chdir(2)然后通过相对路径访问文件。看看我的答案,你会明白如何去做。 - Luis Colorado
@LuisColorado:我做了一些微小的措辞改动,但我认为你的常量名称是反过来的。请参见https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file - Joshua
@LuisColorado: 当然可以。"跨平台"和#elif defined(_WIN32) - Joshua
我可以向你展示数百个Linux程序,它们具有相同的代码行,且未经过交叉编译,但可以将它们移植到Windows。 - Luis Colorado
显示剩余3条评论

1

PATH_MAX通常是一个固定的常量(针对posix系统),它指定了您在系统调用中传递给内核的路径参数的最大长度(例如open(2)系统调用)

但是,文件可以拥有的最大路径长度不受此常量的影响,您可以使用此简单的shell脚本进行测试:

$ i=0
> while [ "$i" -lt 2000 ]
> do mkdir "$i"
>    cd "$i"
>    i=`expr "$i" + 1`
> done
$ pwd
/home/lcu/0/1/2/3/4/5/6/7/8/9/[intermediate output not shown...]/1996/1997/1998/1999

Now, if you try:

$ cd `pwd`
-bash: cd: /home/lcu/0/1/2/3/4/5/6/7/8/9/[intermediate output not shown...]/1996/1997/1998/1999: File name too long

显示出问题在于bash尝试执行chdir(2)系统调用。(下面有一个小程序,展示了如何打开文件名比PATH_MAX常量更长的文件)
你将进入一个目录,它的名称中包含的字符比PATH_MAX常量要多得多。这并不意味着您不能从根目录访问该文件,但您必须这样做:
更改当前目录(如脚本所示) 使用openat(2)和friends来使用一个更接近的打开目录作为起点来访问它。文件路径元素的绝对数量仅受文件系统中inode的数量和总容量的限制。
编辑
当只有#include 并且使用FILENAME_MAX就足够大以容纳任何文件路径时?
唯一支持在 POSIX 操作系统中打开任意文件长度并同时具有可移植性的方法是将路径分成足够短的路径块,并使用 n-1 个 chdir(2) 到达您的文件实际位置。然后,使用最后一个块调用 open(2) 系统调用,如果您的系统支持,则使用 fchdir(2) 系统调用返回到您所在的目录。 如果您有 openat(2) 系统调用,则可以使用 opendir() 调用打开目录(因为您只使用它来打开下一个目录),并关闭前一个目录,以便足够接近最终目录,以便使用 openat(2) 系统调用打开它。
以下是一个(正在进行中的)myfopen() 调用,尝试从 中的 PATH_MAX 限制中打开一个长于此限制的文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "myfopen.h"

FILE *my_fopen(const char *filename, const char *mode)
{
    if (strlen(filename) > PATH_MAX) {
        char *work_name = strdup(filename);
        if (!work_name) {
            return NULL; /* cannot malloc */
        }
        char *to_free   = work_name;  /* to free at end */
        int dir_fd      = -1;
        while(strlen(work_name) >= PATH_MAX) {
            char *p = work_name + PATH_MAX - 1;
            while(*p != '/') p--;
            /* p points to the previous '/' to the PATH_MAX limit */
            *p++ = '\0';
            if (dir_fd < 0) {
                dir_fd = open(work_name, 0);
                if (dir_fd < 0) {
                    ERR("open: %s: %s\n", work_name,
                            strerror(errno));
                    free(to_free);
                    return NULL;
                }
            } else {
                int aux_fd = openat(dir_fd, work_name, 0);
                close(dir_fd);
                if (aux_fd < 0) {
                    ERR("openat: %s: %s\n", work_name,
                        strerror(errno));
                    free(to_free);
                    return NULL;
                }
                dir_fd = aux_fd;
            }
            work_name = p;
        }
        /* strlen(work_name) < PATH_MAX,
         * work_name points to the last chunk and
         * dir_fd is the directory to base the real fopen
         */
        int fd = openat(
                dir_fd,
                work_name,
                O_RDONLY); /* this needs to be
                            * adjusted, based on what is
                            * specified in string mode */
        close(dir_fd);
        if (fd < 0) {
            fprintf(stderr, "openat: %s: %s\n",
                    work_name, strerror(errno));
            free(to_free);
            return NULL;
        }
        free(to_free);
        return fdopen(fd, mode);
    }
    return fopen(filename, mode);
}

你需要使用适当的位掩码集来传递给最终的openat()调用,以遵守fopen()open()调用指定打开模式的不同方式。

最大文件名长度的基本问题(为什么内核设计者不支持无界缓冲区来容纳文件的完整名称)是基于安全的。如果允许用户将一个20GB长的文件名传递到内核空间中,可能会耗尽内核内存空间,这是不允许的(这应该是系统中的一种严重漏洞,因为恶意用户可以通过传递非常长的文件名来阻塞整个内核)。

在正常情况下,我从未处理过超过1024字节的文件,除了演示这个特定的问题,所以我认为你应该接受这个限制,并努力使用短文件名(短于1024是一个很好的限制)。

另一方面,你在这里提到了许多常量:

  • FILENAME_MAX被stdio系统使用,但它的值只能与stdio例程一起使用。它的文档说明如下:
这个宏常量扩展为一个整数表达式,对应于数组char元素所需的大小,以容纳库允许的最长文件名字符串。或者,如果库没有这样的限制,则设置为用于保存文件名的字符数组的建议大小。这意味着这是一个安全的长度,您可以将其与fopen(3)配合使用并传递一个有效的文件名。 PATH_MAX是您可以在系统上使用的安全值。如果尝试打开文件(使用open(2)系统调用)或擦除(unlink(2)),重命名等操作,如果文件名超过此长度,则可能会遇到问题。
文件路径长度的限制来自系统调用子系统的限制,而不是操作涉及的文件系统,因此通常该限制适用于系统中的所有文件,而不仅仅是特定已挂载文件系统的文件(这些文件系统可以进一步受到限制,也可能不受限制)。
在我看来,这些限制非常合理,使用已发布的值对于大多数情况来说都是可以的。而且操作系统中没有任何工具能够打开路径超过PATH_MAX的文件。

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