argv 中指向字符串的指针是否可以修改?

Are the pointers to strings in argv modifiable?

最近(2016 年 1 月,以防问题持续时间足够长)我们遇到了问题
回答的评论部分,我们(@2501 和我)争论是否真的是 字符串 (示例字符是 **argv)可修改或 指向字符串的指针 (示例指针为 *argv)。

适当的标准引用来自 C11 标准草案 N1570,§5.1.2.2.1/2:

The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination.

argv指向字符串的指针是否可以修改?

指针是否可修改取决于指针的constness。参数 argv 声明为 char *argv[]char **argv。他们是否将其视为 char *const argv[] 取决于环境(我不知道)。

正如 OP 在问题中引用的那样,C11 标准明确指出 argcargv 变量以及 argv 数组指向的字符串是可修改的。这些指针是否可修改,是眼前的问题。该标准似乎没有以一种或另一种方式明确说明它。

标准中的写法有两个重点需要注意:

  1. 如果指针应该是不可变的,标准可以通过要求将 main 声明为 int main(int argc, char *const argv[]) 来明确说明,如 在另一个答案中问题。

    标准中没有任何地方提到 constargv 相关的事实似乎是故意的。也就是说,缺少 const 似乎不是可选的,而是标准规定的。

  2. 标准调用argv一致的数组。修改数组是指修改其成员。因此,标准中的措辞似乎很明显是指修改 argv 数组中的成员,而它声明 argv 是可修改的。

    另一方面,C 中的数组 参数(基于 C11 草案 N1570,§6.7.6.3p7)"shall be adjusted to 'qualified pointer to type'".因此,下面的代码,

    int foo(int x[2], int y[2])
    {
        if (x[0] > y[0])
            x = y;
        return x[1];
    }
    

    是有效的 C11,因为 xy 分别调整为 int *xint *y。 (在 C11 草案 N1570,§6.3.2.1p3 中也重申了这一点:"...数组...转换为指向数组初始元素的 'pointer to type' 类型的表达式...".) 显然,如果 xy 被声明为局部或全局数组,而不是函数参数,情况就不一样了。

就语言律师主义而言,我想说标准并没有以这种或那种方式说明,尽管它暗示指针也应该是可修改的。因此,作为对 OP 的回答:both.


实际上,argv 数组中的指针是可修改的,这一传统由来已久。许多库都有初始化函数,这些函数接受指向 argc 的指针和指向 argv 数组的指针,其中一些确实修改了 argv 数组中的指针(删除特定于库的选项);例如 GTK+ gtk_init() and MPI_Init()(尽管至少 OpenMPI 明确声明它不会检查或修改它们)。寻找参数声明(int *argc, char ***argv);这样做的唯一原因——假设意图是使用 (&argc, &argv)main() 调用——是修改指针,从命令中解析和删除特定于库的命令行参数-行参数,根据需要修改 argcargv 中的指针。

(我最初说 POSIX 中的 getopt() 设施依赖于 指针是可修改的——这个特性可以追溯到 1980 年,被大多数 Unix 风格,并在 1997 年在 POSIX.2 中标准化——但这是不正确的,正如 Jonathan Leffler 在评论中指出的那样:POSIX getopt() 不修改实际指针;仅GNU getopt() 会,而且只有当 POSIXLY_CORRECT 环境变量没有设置时才会发生。GNU getopt_long() 和 BSD getopt_long() 都会修改指针,除非 POSIXLY_CORRECT 被设置,但与 getopt() 相比,它们更年轻,也没有那么普遍。)

在 Unix 领域,"portable" 修改 argv[] 数组指向的字符串的内容,并使修改后的字符串在进程列表中可见。 DJB 的 daemontools 包 readproctitle 就是一个有用的例子。 (请注意,必须就地修改字符串,并且不能扩展,以便在进程列表中显示更改。)

所有这些都表明了一个非常悠久的传统,基本上几乎是自 C 作为编程语言诞生以来,并且肯定先于 C 的标准化,处理 argcargv 中的指针argv 数组,以及这些指针指向的字符串的内容,可修改。

因为 C 标准的目的不是定义新的行为,而是将现有的行为编成代码(以促进可移植性和可靠性等),似乎可以安全地假设这是部分无意的遗漏标准编写者没有明确指定 argv 数组中的指针是可修改的。其他任何东西都会打破传统,并且明确违反 POSIX 标准(该标准也旨在促进跨系统的可移植性,并扩展 ISO C 标准中未包含的 C 功能)。