2

我需要一位有权威来源的语言律师。

看看下面在 gcc 下编译干净的测试程序:

#include <stdio.h>


void foo(int *a) {
    a[98] = 0xFEADFACE;
}

void bar(int b[]) {
    *(b+498) = 0xFEADFACE;
}

int main(int argc, char **argv) {

int a[100], b[500], *a_p;

*(a+99) = 0xDEADBEEF;
*(b+499) = *(a+99);

foo(a);
bar(b);

printf("a[98] == %X\na[99] == %X\n", a[98], a[99]);
printf("b[498] == %X\nb[499] == %X\n", b[498], b[499]);

a_p = a+98;
*a_p = 0xDEADFACE;

printf("a[98] == %X\na[99] == %X\n", a[98], a[99]);

}

它产生我期望的输出:

anon@anon:~/study/test_code$ gcc arrayType.c -o arrayType
anon@anon:~/study/test_code$ ./arrayType 
a[98] == FEADFACE
a[99] == DEADBEEF
b[498] == FEADFACE
b[499] == DEADBEEF
a[98] == DEADFACE
a[99] == DEADBEEF

a 和 b 是同一类型吗?被处理为与编译器内部 int *a相同的类型?int a[]

从实际的角度来看,int a[100], b[500], *a_p, b_a[];所有这些似乎都是同一类型。在我上面的例子中,我很难相信编译器会在各种情况下不断地调整这些类型。我很高兴被证明是错误的。

有人可以明确详细地为我解决这个问题吗?

4

11 回答 11

9

a 和 b 是同一类型吗?int *a 是否在编译器内部处理为与 int a[] 相同的类型?

从常见问题解答comp.lang.C

...只要数组出现在表达式中,编译器就会隐式生成指向数组第一个元素的指针,就像程序员编写了 &a[0] 一样。(例外情况是数组是 sizeof 或 & 运算符的操作数,或者是字符数组的字符串字面量初始值设定项...)

... 给定一个数组 a 和指针 p,a[i] 形式的表达式会导致数组按照上述规则衰减为一个指针,然后像表达式 p 中的指针变量一样被下标[i](虽然最终的内存访问会有所不同......

鉴于声明

char a[] = "hello";
char *p = "world";

...当编译器看到 expression 时a[3],它会发出从 location 开始的代码a,将三个移过它,然后从那里获取字符。当它看到表达式p[3]时,它会发出从 location 开始的代码,在p那里获取指针值,将指针加 3,最后获取指向的字符。换句话说,a[3]是 3 位过去(开始)名为 的对象a,而p[3]是 3 位以上 指向的对象p

重点是我的。最大的区别似乎是当指针是指针时会获取指针,而如果是数组则没有指针可获取。

于 2009-09-03T11:33:41.180 回答
3

差异之一 -int a[x][y]并且int **a不可互换。

http://www.lysator.liu.se/c/c-faq/c-2.html

2.10:

数组数组(即C 中的二维数组)衰减为指向数组的指针,而不是指向指针的指针。

于 2009-09-03T11:56:40.470 回答
3

ab都是整数数组。a[0]不是包含内存地址的内存位置,它是包含int的内存位置。

数组和指针既不完全相同也不可互换。当表达式中出现的 T 型数组类型的左值衰减(除了三个例外)成为指向其第一个元素的指针时,数组等效指针;结果指针的类型是指向 T 的指针。在查看相关代码的汇编输出时,这一点变得很清楚。三个例外,fyi,是当数组是sizeof&的操作数或字符数组的文字字符串初始值设定项时。

如果你想象这个:

char a[] = "hello";
char *p = "world";

将产生可以像这样表示的数据结构:

   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+

   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

并意识到像 x[3] 这样的引用会产生不同的代码,具体取决于 x 是指针还是数组。编译器的 a[3] 意味着:从位置 a 开始并移动三个过去并在那里获取字符。p[3] 表示转到位置 p,取消对那里的值的引用,将三个移过它并在那里获取字符。

于 2009-09-03T12:16:15.567 回答
3

来自C 语言标准

6.3.2.1.3 除非是 sizeof 运算符的操作数或
          一元 & 运算符,或者是用于初始化的字符串文字
          一个数组,一个具有“类型数组”类型的表达式是
          转换为类型为 ''pointer to type'' 的表达式
          指向数组对象的初始元素,而不是
          一个左值。如果数组对象有寄存器存储类,则
          行为未定义。

假设以下代码:

#include <stdio.h>
#include <string.h>
int main(void)
{
  char foo[10] = {0};
  char *p = foo;
  foo[0] = 'b';
  *(foo + 1) = 'a';
  strcat(foo, "t");
  printf("foo = %s, &foo = %p, &p = %p, sizeof foo = %lu, sizeof p = %lu\n", 
    foo, &foo, &p, (unsigned long) sizeof foo, (unsigned long) sizeof p);
  return 0;
}

foo 被声明为一个 10 元素的 char 数组,所有元素都初始化为 0。 p 被声明为指向 char 的指针并被初始化为指向 foo。

在行

char *p = foo;

表达式 foo 的类型为“10 元素 char 数组”;由于 foo 不是 sizeof 或 & 的操作数,也不是用于初始化数组的字符串文字,因此它的类型被隐式转换为“指向 char 的指针”并设置为指向数组的第一个元素。该指针值被复制到 p。

在行中

foo[0] = 'b';
*(foo + 1) = 'a';

表达式 foo 的类型为“10 元素 char 数组”;由于 foo 不是 sizeof 或 & 的操作数,也不是用于初始化数组的字符串文字,因此它的类型被隐式转换为“指向 char 的指针”并设置为指向数组的第一个元素。下标表达式被解释为“`*(foo + 0)”。

在行

strcat(foo, "t");

foo 的类型为“10-element array of char”,字符串文字“t”的类型为“2-element array of char”;因为既不是 sizeof 也不是 & 的操作数,虽然 "t" 是字符串文字,但它不用于初始化数组,两者都被隐式转换为类型 "pointer to char",并且指针值被传递给字符串()。

在行

  printf("foo = %s, &foo = %p, &p = %p, sizeof foo = %lu, sizeof p = %lu\n", 
    foo, &foo, &p, (unsigned long) sizeof foo, (unsigned long) sizeof p);

如上所述,foo 的第一个实例被转换为指向 char 的指针。foo 的第二个实例是 & 运算符的操作数,因此它的类型不会转换为“指向 char 的指针”,而表达式“&foo”的类型是“指向 char 的 10 元素数组的指针”或“char ( *)[10]”。将此与表达式“&p”的类型类型进行比较,即“指向 char 的指针”或“char **”。foo 的第三个实例是 sizeof 运算符的操作数,因此它的类型再次没有转换,并且 sizeof 返回分配给数组的字节数。将此与 sizeof p 的结果进行比较,后者返回分配给指针的字节数。

每当有人告诉您“数组只是一个指针”时,他们就会混淆上面引用的标准中的部分。数组不是指针,指针也不是数组;但是,在许多情况下,您可以将数组视为指针,也可以将指针视为数组。在第 6、7 和 8 行中,“p”可以替换“foo”。但是,它们不能作为 sizeof 或 & 的操作数互换。

编辑:顺便说一句,作为函数参数,

void foo(int *a);

void foo(int a[]);

是等价的。“a[]”被解释为“ *a”。请注意,这适用于函数参数。

于 2009-09-03T15:23:56.100 回答
2

我同意 sepp2k 的回答和 Mark Rushakoff 的 comp.lang.c 常见问题解答报价。让我添加两个声明之间的一些重要区别和一个常见的陷阱。

  1. 当您定义a为数组时(在函数参数以外的上下文中,这是一种特殊情况),您不能写 a = 0; 或一个++;因为a不是左值(可以出现在赋值运算符左侧的值)。

  2. 数组定义保留空间,而指针没有。因此,sizeof(array)将返回存储所有数组元素所需的内存空间(例如,对于 32 位架构上的 10 个整数数组,10 乘以 4 个字节),而sizeof(pointer)仅返回存储该指针所需的内存空间(例如64 位架构中的 8 个字节)。

  3. 当您预先添加指针或附加数组声明时,事情肯定会有所不同。例如,int **a是一个指向整数的指针。它可以用作二维数组(具有不同大小的行),方法是分配一个指向行的指针数组并将每个指针指向内存以存储整数。访问a[2][3]编译器将获取指针,a[2]然后将三个元素移动到它指向的位置之外,以便访问该值。相比之下,b[10][20]它是一个由 10 个元素组成的数组,每个元素是一个由 20 个整数组成的数组。访问b[2][3]编译器将通过将 2 乘以 20 个整数的大小并再加上 3 个整数的大小来偏移数组内存区域的开头。

最后,考虑一下这个陷阱。如果你有一个 C 文件

int a[10];

在另一个

extern int *a;
a[0] = 42;

文件将编译和链接而不会出现错误,但代码不会按照您的预期执行;它可能会因空指针分配而崩溃。原因是在第二个文件中 a 是一个指针,其值是第一个文件的内容a[0],即最初为 0。

于 2009-09-03T11:47:40.053 回答
2

看这里:

2.2:但是我听说 char a[] 和 char *a 是一样的。

http://www.lysator.liu.se/c/c-faq/c-2.html

于 2009-09-03T11:57:22.043 回答
1

您的示例中有两个 a 和两个 b。

作为参数

void foo(int *a) {
    a[98] = 0xFEADFACE;
}

void bar(int b[]) {
    *(b+498) = 0xFEADFACE;
}

a 和 b 属于同一类型:指向 int 的指针。

作为变量

int *a;
int b[10];

不属于同一时间。第一个是指针,第二个是数组。

数组行为

数组(变量或非变量)在大多数上下文中被隐式转换为指向其第一个元素的指针。C 中没有完成的两个上下文是 sizeof 的参数和&;的参数。在 C++ 中,还有一些与引用参数和模板相关的内容。

我写了,变量与否,因为转换不仅针对变量进行,一些示例:

int foo[10][10];
int (*bar)[10];
  • foo是一个由 10 个 10 个整数组成的数组。在大多数情况下,它将转换为指向其第一个元素的指针,类型为指向 10 int 数组的指针

  • foo[10]是一个 10 个 int 的数组;在大多数情况下,它将转换为指向其第一个元素的指针,类型为指向 int的指针。

  • *bar是一个 10 个 int 的数组;在大多数情况下,它将转换为指向其第一个元素的指针,类型为指向 int的指针。

一些历史

在 B 中,C 的直系祖先,相当于

int x[10];

具有我们在当前 C 中编写的效果

int _x[10];
int *x = &_x;

即它分配内存并初始化一个指向它的指针。有些人似乎误解了它在 C 中仍然是正确的。

在 NB 中——当 C 不再是 B 但还没有被称为 C 时——有一段时间声明了一个指针

int x[];

int foo[10];

将具有当前含义。功能参数的调整是那个时代的残余。

于 2009-09-03T13:18:35.190 回答
0

a 和 b 是同一类型吗?

是的。[编辑:我应该澄清一下:函数 foo 的参数 a 与函数 bar 的参数 b 类型相同。两者都是指向 int 的指针。main 中的局部变量 a 与 int 中的局部变量 b 类型相同。两者都是整数数组(实际上它们不是同一类型,因为它们的大小不同。但都是数组)。]

int *a 是否在编译器内部处理为与 int a[] 相同的类型?

通常不会。例外情况是,当您将foo bar[]其作为参数写入函数时(就像您在此处所做的那样),它会自动变为foo *bar.

然而,在声明非参数变量时,有很大的不同。

int * a; /* pointer to int. points nowhere in paticular right now */
int b[10]; /* array of int. Memory for 10 ints has been allocated on the stack */
foo(a); /* calls foo with parameter `int*` */
foo(b); /* also calls foo with parameter `int*` because here the name b basically
           is a pointer to the first elment of the array */
于 2009-09-03T11:25:21.230 回答
0

不,他们不一样!一个是指向 int 的指针,另一个是 100 个 int 的数组。所以是的,它们是一样的!

好的,我将尝试解释这种愚蠢。

*a 和 a[100] 对于你正在做的事情基本上是一样的。但是,如果我们详细查看编译器的内存处理逻辑,我们要说的是:

  • *a 编译器,我需要内存,但我会在稍后告诉你,所以现在冷静一下!
  • a[100] 编译器,我现在需要内存知道我需要 100,所以确保我们有它!

两者都是指针。您的代码可以对它们一视同仁,并随心所欲地践踏这些指针附近的内存。但是,a[100]是在编译时分配的指针的连续内存,而 *a 仅分配指针,因为它不知道您何时需要内存(运行时内存噩梦)。

那么,谁在乎,对吧?嗯,某些功能,如sizeof()护理。 sizeof(a)将返回一个不同的答案 for*a和 for a[100]。这在功能上也会有所不同。在这种函数情况下,编译器知道其中的区别,因此您也可以在代码中使用它来发挥自己的优势,例如 for 循环、memcpy 等。继续尝试。

这是一个很大的问题,但我在这里给出的答案是这样的。编译器知道细微的差别,它会生成大多数时候看起来一样的代码,但在重要的时候会有所不同。由您决定 *a 或 a[100] 对 cimpiler 意味着什么,以及它将在哪里以不同的方式处理它。它们实际上可以相同,但它们并不相同。更糟糕的是,你可以通过调用你所拥有的函数来改变整个游戏。

呼……难怪像 c# 这样的托管代码现在这么火?!

编辑: 我还应该补充一点,您可以这样做*a_p = X,但请尝试使用您的阵列之一来做到这一点!数组像指针一样使用内存,但它们不能移动或调整大小。像这样的指针*a_p可以指向不同的东西。

于 2009-09-03T11:50:00.220 回答
0

我将把我的帽子扔进戒指来简单解释一下:

  • 数组是同一类型的一系列连续存储位置

  • 指针是单个存储位置的地址

  • 取一个数组的地址给出了它的第一个元素的地址(即一个指针)。

  • 可以通过指向数组第一个元素的指针来访问数组的元素。这是有效的,因为下标运算符 [] 以一种旨在促进这一点的方式在指针上定义。

  • 可以在需要指针参数的地方传递一个数组,它将自动转换为指向第一个元素的指针(尽管这对于多级指针或多维数组不是递归的)。同样,这是设计使然。

因此,在许多情况下,同一段代码可以对未分配为数组的数组和连续内存块进行操作,因为数组和指向其第一个元素的指针之间的特殊关系。然而,它们是不同的类型,并且在某些情况下它们的行为确实不同,例如指向数组的指针与指向指针的指针完全不同。

这是一个最近的 SO 问题,涉及到指针到数组与指针到指针的问题:C 中“abc”和 {“abc”} 之间有什么区别?

于 2009-09-03T12:34:19.190 回答
0

如果您有一个指向字符数组的指针(并且想要获取该数组的大小),则不能使用 sizeof(ptr) 而必须使用 strlen(ptr)+1!

于 2009-09-03T13:08:29.313 回答