【问题标题】:Are the pointers to strings in argv modifiable? [duplicate]argv 中指向字符串的指针是否可以修改? [复制]
【发布时间】:2016-01-30 19:13:26
【问题描述】:

最近(2016 年 1 月,如果问题持续时间足够长的话)我们遇到了问题Are the strings in argv modifiable?
this 答案的评论部分,我们(@2501 和我)争论到底是字符串(示例字符是**argv)是可修改的还是指针到字符串(示例指针为*argv)。

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

参数argcargv以及指向的字符串 argv 数组应可由程序修改,并保留其 程序启动和程序终止之间的最后存储值。

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

【问题讨论】:

  • @nsilent22 正如我在引用的问题中已经说过的,在 C 中,您也可以执行 char* str = "foo";*str = 'c'; 是未定义的行为。所以我会把const留在这里。
  • @SimonShine: argv 是一个局部变量,所以无论如何它都是可以修改的。但问题是关于*argv 和其他指针。
  • 虽然问题本身很好,但我个人认为改变论点和*argv 是不好的风格。它可能对独立环境有用,但标准并未定义启动行为/功能,因此无论如何它取决于您的实现和环境。
  • @Olaf 好吧,几乎所有的挑剔问题和关于标准的问题都来自糟糕的风格和极端情况,不是吗?标准不是在托管实现上定义int main(void)int main(int, char**) 吗?

标签: c pointers language-lawyer argv


【解决方案1】:

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

标准中的措辞有两点需要注意:

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

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

  2. 标准调用argv 始终是一个数组。修改数组是指修改其成员。因此,标准中的措辞显然是指修改argv 数组中的成员,而它声明argv 是可修改的。

    另一方面,C 中的数组参数(基于 C11 草案 N1570, §6.7.6.3p7)“应调整为'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 中重申:"... array ... 被转换为类型为“pointer to type”的表达式,指向数组的初始元素.. 。”。) 显然,如果 xy 被声明为局部或全局数组,而不是函数参数,则情况并非如此。

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


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

(我最初声明 POSIX 中的 getopt() 工具依赖可修改的指针——该功能可追溯到 1980 年,被大多数 Unix 风格采用,并在 POSIX.2 中标准化1997 - 但这是不正确的,正如 Jonathan Leffler 在评论中指出的那样:POSIX getopt() 不会修改实际指针;只有 GNU getopt() 会这样做,而且只有在未设置 POSIXLY_CORRECT 环境变量时才会这样做。 GNU getopt_long() 和 BSD getopt_long() 会修改指针,除非设置了 POSIXLY_CORRECT,但与 getopt() 相比,它们更年轻且不那么普遍。)

在 Unix 领域,修改 argv[] 数组所指向的字符串的内容被认为是“可移植的”,并让修改后的字符串在进程列表中可见。 DJB 的 daemontools 包readproctitle 就是其中一个有用的例子。 (请注意,必须在原地修改字符串,并且不能扩展,才能使更改在进程列表中可见。)

所有这一切都表明了一个很长的传统,基本上几乎是从 C 作为编程语言诞生以来,并且肯定在 C 标准化之前,处理 argcargvargv 数组中的指针,以及这些指针指向的字符串的内容,可以修改。

因为 C 标准的意图不是定义新的行为,而是编纂跨实现的现有行为(以促进可移植性和可靠性等),因此可以安全地假设它是标准的一部分无意遗漏编写者不要明确指定 argv 数组中的指针为可修改的。其他任何事情都会打破传统,并明显违反 POSIX 标准(该标准还旨在促进跨系统的可移植性,并扩展 ISO C 标准中未包含的 C 功能)。

【讨论】:

  • 标准getopt()不依赖argv是可修改的; GNU getopt() 这样做是因为它置换了参数列表。
  • @JonathanLeffler:该死,真的!我什至检查了一些旧的 Unix getopt() 实现,它们也保持参数列表不变。只有 GNU getopt() 修改了指针。 (尽管 GNU 和 BSD getopt_long() 都会修改指针,即使它们被标记 const,除非设置了 POSIXLY_CORRECT 环境变量。)我必须更正我的答案。
  • 我将@JonathanLeffler 指出的关于getopt() 的不正确部分替换为对GTK+ 和MPI 初始化函数的引用(以及您可以查找其他库初始化函数的签名)。如果您(或其他任何人)发现任何其他错误,请务必指出。
  • 点#1 的const 参数被反驳,因为constmain() 之后很长时间被添加到C。通过添加const 更改main() 签名会破坏现有代码。增强#2 本身就足够了。
【解决方案2】:

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

【讨论】:

  • 但是你真的可以仅仅因为知道指针不是const就可以声明它们是可写的吗?请参阅my comment 这个问题。
  • @cad;您问了一个关于修改指针的问题,而不是它指向的内容:指向 argv 中的字符串的指针是否可以修改?char const *achar *const a 含义不同。
  • 但是你回答的前两句话告诉我什么?他们基本上告诉我指向字符串的指针是可修改的,因为它们是相应声明的。所以你说它们可以根据实现进行修改?
  • @cad;如果你声明char * p = "abcd",那么你不能做p[0] = 'e',但你可以修改指针p本身。 p = "Hello"。但是,如果在某些环境中,无论如何,char * p = "abcd" 被解释为char * const p,那么它不可能修改p
猜你喜欢
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-08
  • 2013-06-19
  • 2018-04-05
  • 1970-01-01
  • 2019-03-16
相关资源
最近更新 更多