argv是指向指针数组的指针。

8

我对下面这段话如何与后面的代码相匹配感到困惑:

由于 argv 是指向指针数组的指针,我们可以操纵指针而不是索引数组。下一个变体基于递增 argv(它是指向指向 char 的指针的指针) ,而 argc 则递减:

#include <stdio.h>
/* echo command-line arguments; 2nd version */
main(int argc, char *argv[])
{
    while (--argc > 0)
        printf("%s%s", *++argv, (argc > 1) ? " " : "");
    printf("\n");
    return 0;
}

“char *argv[]”不就是指针数组吗?指向指针数组的指针不应该写成“char *(*argv[])”或类似的形式吗?

顺便说一下,通常情况下,我发现混合使用数组和指针的声明相当令人困惑,这正常吗?

6个回答

12

“指向数组的指针”或“指向数组”这些术语在C编程术语中常常被非常宽泛地使用。它们可以表示至少两种不同的含义。

从最为严格和学究的角度来讲,“指向数组的指针”必须以“指向数组”的类型声明,就像下面这样:

int a[10];
int (*p)[10] = &a;
在上面的示例中,p 被声明为指向包含10个 int 元素的数组的指针,并实际初始化为指向这样一个数组。
然而,该术语通常也以其非正式的含义使用。在本例中。
int a[10];
int *p = &a;

p被声明为指向整数的简单指针。它初始化为指向数组a的第一个元素。即使情况在语义上与先前的情况不同,人们经常会听到和看到人们说这种情况下p也“指向一个由int组成的数组”,这意味着通过指针算术提供对数组元素的访问,比如p [5]*(p + 3)

这正是你引用的短语“…argv是指向指针数组的指针…”所指的。在main函数的参数列表中,argv的声明等效于char **argv,这意味着argv实际上是一个指向char *指针的指针。但是,由于它物理上指向某个由char *指针组成的数组的第一个元素(由调用代码维护),因此半正式地说argv指向一个指针数组是正确的。

这正是引用的文本所指的。


5

当C语言函数声称接受数组时,实际上它们严格接受指针。该语言不区分void fn(int *foo) {}void fn(int foo[])。即使你有void fn(int foo[100]),然后传递一个大小为int [10]的数组也无所谓。

int main(int argc, char *argv[])

等同于

int main(int argc, char **argv)

因此,argv指向一个char指针数组的第一个元素,但它本身不是一个数组类型,也没有(正式地)指向整个数组。但我们知道那个数组在那里,我们可以索引它以获得其他元素。
在更复杂的情况下,比如接受多维数组时,只有第一个[]会回到指针(并且可以保留未定义大小)。其他部分仍然作为被指向的类型的一部分,并且它们会影响指针算术运算。

2

数组指针等价只适用于函数参数,因此尽管 void fn(const char* argv[])void fn(const char** argv) 是等价的,但当涉及要传递到函数中的变量时,它并不成立。

考虑以下示例:

void fn(const char** argv)
{
    ...
}

int main(int argc, const char* argv[])
{
    fn(argv); // acceptable.

    const char* meats[] = { "Chicken", "Cow", "Pizza" };

    // "meats" is an array of const char* pointers, just like argv, so
    fn(meats); // acceptable.

    const char** meatPtr = meats;
    fn(meatPtr); // because the previous call actually cast to this,.

    // an array of character arrays.
    const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
    fn(vegetables); // does not compile.

    return 0;
}

“vegetables”不是指向指针的指针,它直接指向一个3*10连续字符序列中的第一个字符。将上面的fn(vegetables)替换为以下内容:
int main(int argc, const char* argv[])
{
    // an array of character arrays.
    const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
    printf("*vegetables = %c\n", *(const char*)vegetables);

    return 0;
}

输出结果为"A":vegetables本身直接指向字符,没有中间指针。

蔬菜赋值基本上是这个的快捷方式:

const char* __vegetablesPtr = "Avocado\0\0\0Pork\0\0\0\0\0\0Pepperoni\0";
vegetables = __vegetablesPtr;

并且

const char* roni = vegetables[2];

翻译为

const char* roni  = (&vegetables[0]) + (sizeof(*vegetables[0]) * /*dimension=*/10 * /*index=*/2);

0
“指向数组第一个元素的指针”是一种常见的结构。每个字符串函数都使用它,包括输入和输出字符串的stdio函数。main用于argv。
“指向数组的指针”是一种罕见的结构。我在C标准库或POSIX中找不到任何用途。在本地安装的所有头文件中(对于'([^)]*\*[^)]) *\['),我发现指向数组的指针只有两个合法实例,一个在libjpeg中,另一个在gtk中。(两者都是结构成员,而不是函数参数,但这并不重要。)
因此,如果我们坚持使用官方语言,我们有一个罕见的东西,它有一个短名称和一个类似但更常见的东西,它有一个长名称。这与人类语言自然运作的方式相反,因此存在紧张关系,在除了最正式的情况下通过“错误地”使用短名称来解决。
我们之所以不直接说“指向指针”的原因是,指针还有另一种常见的用途,即作为函数参数时,该参数指向不是数组成员的单个对象。例如,在
long strtol(const char *nptr, char **endptr, int base);

endptr 的类型与 main 中的 argv 完全相同,都是指向指针的指针,但它们的用法不同。 argv 指向一个 char * 数组中的第一个元素;在 main 函数中,您需要使用索引(如 argv[0]argv[optind] 等)或通过使用 ++argv 来递增数组。

endptr 指向单个 char *。在 strtol 中,递增 endptr 或引用 endptr[n](其中 n 不为零)都没有用。

这种语义上的差异通过非正式用法“argv 是指向数组的指针”来表达。虽然可能会与正式语言中“指向数组的指针”的含义混淆,但忽略了这一点,因为使用简洁语言的本能比遵循正式定义更强烈,而后者告诉您不要使用最明显的简单短语,因为它被保留用于几乎永远不会发生的情况。


0

由于 argv 是指向指针数组的指针

这是错误的。 argv 是一个指针数组。


我来到这里发表这个想法,所以给你点赞。此外,将其写为char* argv[]可能更有意义,因为它更容易阅读作为指针数组。 - austin
1
不,这也是错误的,argv 是指针指向指针。要考虑 sizeof()、将 argv 变量分配给其他内容的可能性等。 - user529758
@H2CO3,C语言中argv被称为指针数组。但严格来说,main函数的形式参数会被调整为指向指针的指针。 - ouah
@ouah,那么标准的措辞是不正确的吗?在C语言中,函数参数不可能是数组,它总是会衰变成指针。 - user529758
@H2CO3 我对他们在那个特定情况下的措辞也不是很满意。 - ouah

0
由于argv是指向指针数组的指针,
不,根本不是。

char *argv[]不只是一个指针数组吗?

不,它是指向指针的指针。

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