main()函数第二个参数中char *argv[]和char **argv的区别是什么?

30

代码 1

#include<stdio.h>

int main(int argc, char *argv[])
{
int j;
printf("%d", argv[1][0]);
return 0;
}

代码2

#include<stdio.h>

int main(int argc, char **argv)
{
int j;
printf("%d", argv[1][0]);
return 0;
}
CODE 1 和 CODE 2 的输出一样,但是 CODE 1CODE 2 中 main 函数的第二个参数不同。在编译时,在数据段上方创建指针数组。argv 是指针数组,因此我们应该将 main 函数中的参数声明为字符型指针的指针,即 **argv。请问 CODE 1 的参数声明方式正确吗?

8
就编译器而言,第二个参数在任何方面上都没有不同,也就是说,char *argv[]char **argv是等价的。这更像是一种偏好的编码风格问题。 - Grzegorz Szpetkowski
4个回答

27

对于C语言来说,char** xchar* x[]这两种方式是表达同样的意思的基本概念。它们都声明参数接受一个指向指针数组的指针。请注意,您总是可以编写:

 char *parray[100];
 char **x;

 x = &parray[0];

然后同样使用 x。


12
除了使用 sizeof - Ed Heal
3
如果您至少简要说明一下,在C语言中有一些地方(例如Ed Heal提到的sizeof)数组与指针不等效,以及函数参数声明中的“数组调整”(问题所询问的内容)与表达式中的“数组衰减”是不同但相关的,那么这个答案将会显著改善。您可以引用例如What's a modern term for “array/pointer equivalence”?Exception to array not decaying into a pointer?作为参考。 - Ilmari Karonen
@Ilmari-Karonen 在我看来,这个回答是正确的(然而例子是错误的)。只因为一个函数签名中有花括号并不意味着它将成为一个数组变量!事实上,C 会默认它是一个普通指针!这甚至是人们可以从 C 中期望的唯一合乎逻辑的决定,因为如果真的假设它是一个数组变量,就没有任何东西能够分配给它(数组变量没有像指针那样特有的单独内存)。所以我相信这只是由 C 提供的简化符号(char* arr[]),它与另一种符号(char** arr)完全相同。 - aderchox

14

基本上,char* argv[] 表示字符指针的数组,而 char** argv 表示指向字符指针的指针。

在任何数组中,数组的名称是指向数组第一个元素的指针,它包含第一个元素的地址。

因此,在下面给出的代码中,在字符数组 x 中,x 是指向第一个元素 '1' 的指针,它是一个字符。所以它是指向字符的指针。

而在数组 arr 中,arr 是指向第一个元素 x 的指针,x 本身是一个指向字符的指针。因此它是指向另一个指针的指针。

因此,x 是 char*,arr 是 char**。

当在函数中接收某些东西时,基本规则是,您必须告诉您正在接收的东西的类型。因此,您可以简单地说您想要接收一个 char**,或者也可以说 char* arr[]。

在第一种情况下,我们不需要考虑任何复杂的事情。我们只需要知道,我们正在接收一个 char* 数组。难道我们不知道这一点吗?因此,我们接收它并使用它。

在第二种情况下,很简单,就像我上面解释的那样,arr 是 char**,您可以将其作为它的类型并安全地接收它。现在系统知道我们接收到的东西的类型,我们可以通过简单地使用数组注释来访问下一个元素。就好像我们已经接收到了数组的起始地址一样,我们肯定可以访问下一个元素,因为我们知道它的类型,我们知道它包含什么以及如何进一步使用它。我们知道它包含指向字符的指针,因此我们也可以合法地访问它们。

void func1(char* arr[])
{
    //function body
}
void func2(char** arr)
{
    //function body
}

int main()
{
    //x, y and z are pointer to char
    char x[3]={'1', '2', '3'};
    char y[3]={'4', '5', '6'};
    char z[3]={'7', '8', '9'};

    //arr is pointer to char pointer
    char* arr[3]={x, y, z};

    func1(arr);
    func2(arr);
}

9
这个回答存在一些误解,已经让至少一个人感到困惑了。首先,char* argv[]char** argv是完全等价的,根本没有不同的含义。其次,arr不是一个指针。我没有仔细研究文本以确定其他具体问题,但这些问题已经严重到值得投下反对票,并真诚希望您在误导更多人之前重新审视此帖子! - Lightness Races in Orbit
返回仅翻译的文本:(在此上下文中绝对等效,至少) - Lightness Races in Orbit
1
@LightnessRacesinOrbit 关于其次,arr不是指针 - 或许可以注释一下arr是什么以及哪个arr - 我在这个答案中看到了各种用法。 - chux - Reinstate Monica
@chux:好观点。我是在回应代码注释时忘记说了! - Lightness Races in Orbit
我同意这个答案是误导性的!首先,数组名不是指针!它是数组第一个元素的“地址”,它并不包含它!因为它在内存中没有单独的位置!请修正这些错误或删除答案。 - aderchox

10

它们完全相同。C11标准的§5.1.2.2.2规定:

The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

int main(void) { /* ... */ }

or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):

int main(int argc, char *argv[]) { /* ... */ }

or equivalent;10) or in some other implementation-defined manner.

10) Thus, int can be replaced by a typedef name defined as int, or the type of argv can be written as char ** argv, and so on.

很明显,意图是使两个声明相同。此外,该规则在§6.7.6.3/7中进行了描述:
“将参数声明为‘类型的数组’应调整为‘限定符指向类型的指针’,其中限定符(如果有)是在数组类型推导的‘[’和‘]’中指定的...”

1
[编辑] 在评论时使用的可能是GCC 7.2
像这样声明一个数组
char array[]

使它成为常量,这意味着您不能编写以下代码。
char array[] = "hello";
array = "hey";

即使第二个字符串更小,应该适合,但是会出现以下错误:

错误:数组类型'char [6]'不可分配

如果您有**argv,则可以编写:
main(int argc, char **argv)
{
    char **other_array;
    /*
     * do stuff with other_array
     */
    argv = other_array;
}

如果你有*argv[],那么:
main(int argc, char *argv[])
{
    char **other_array;
    /*
     * do stuff with other_array
     */
    argv = other_array;
}

提醒您

警告:将‘char **’类型的值分配给‘const char **’类型的变量会丢弃限定符

因此,从技术上讲,这是一种小优化,就好像您已经写了const一样。


1
什么?当然不是,这是哪个编译器? - Antti Haapala -- Слава Україні
这是3年半前的事了,我假设使用的是gcc 7。但肯定不是使用gcc 10。 - jde-chil

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