如何在C编译器内部处理数组和指针类型? (int * a;与int a ;)

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

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

#include  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[]; 所有似乎都是同一类型。 在上面的例子中,我很难相信编译器会在各种情况下不断调整这些类型。 我很高兴被certificate是错的。

有人能否明确地详细解决这个问题?

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

来自comp.lang.C FAQ :

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

…给定一个数组a和指针p,a [i]forms的表达式会导致数组衰减成指针,遵循上面的规则,然后被下标,就像表达式p中的指针变量一样[i](虽然最终的内存访问会有所不同……

鉴于。的声明

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

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

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

其中一个差异 – int a[x][y]int **a不可互换。

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

2.10:

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

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

数组和指针既不相同也不可互换。 表达式中出现的类型为array-of-T的左值(有三个例外)衰减到指向其第一个元素的指针时,数组等效于指针iff ; 结果指针的类型是指向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开始并移动三个并在那里获取char。 p [3]表示转到位置p,取消引用那里的值,移动三个并在那里取出char。

从C语言标准 :

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

假设以下代码:

 #include  #include  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-element array of char”; 因为foo不是sizeof或&的操作数,并且不是用于初始化数组的字符串文字,所以它的类型被隐式转换为“指向char的指针”并被设置为指向数组的第一个元素。 该指针值被复制到p。

在线

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

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

在线

 strcat(foo, "t"); 

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

在线

  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”的类型是“指向10个元素的char数组”或“char” ( * )[10]“。 将其与表达式“&p”的类型类型进行比较,该表达式是“指向char的指针”或“char ** ”。 foo的第三个实例是sizeof运算符的操作数,因此它的类型不再转换,sizeof返回分配给数组的字节数。 将其与sizeof p的结果进行比较,后者返回分配给指针的字节数。

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

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

 void foo(int *a); 

 void foo(int a[]); 

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

看这里:

2.2:但我听说char a []与char * a相同。

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

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

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

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

  3. 当你在前面添加指针或附加数组声明时,事情肯定会发生分歧。 例如, int **a是指向整数的指针。 它可以用作二维数组(具有不同大小的行),方法是为行指定一个指针数组,并使每一个指向内存以存储整数。 要访问a[2][3] ,编译器将获取a[2]的指针,然后移动三个元素超过它指向的位置以访问该值。 将其与b[10][20]进行对比, 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。

在你的例子中有两个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]; 

会有当前的意思。 函数参数的调整是该时间的残差。

a和b是同一类型吗?

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

int *是否在编译器内部处理与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 */ 

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

好的,我会试着解释这种愚蠢。

* a和a [100]对于你正在做的事情基本相同。 但是如果我们仔细研究编译器的内存处理逻辑,我们所说的是:

  • *a 编译器,我需要记忆,但我会告诉你多久以后,所以现在好冷!
  • a[100] 编译器,我现在需要内存,我知道我需要100个,所以请确保我们拥有它!

两者都是指针 。 而且你的代码可以对它们进行相同的处理,并在你想要的那些指针附近践踏内存。 但是, a[100]是在编译时分配的指针的连续内存,而* a只分配指针,因为它不知道你什么时候需要内存(运行时内存噩梦)。

所以, 谁关心 ,对吧? 好吧,某些function,如sizeof()关心。 sizeof(a)将为*aa[100]返回不同的答案。 这也将在function上有所不同。 在这个函数的情况下,编译器知道差异,所以你也可以在代码中使用它,对于循环,memcpy等等。继续,试试。

这是一个很大的问题,但我在这里给出的答案就是这个。 编译器知道细微差别,它会生成大多数时候看起来相同的代码,但在重要时会有所不同。 由你来找出* a或[100]对cimpiler意味着什么,以及它将以何种方式对待它。 它们可以有效地相同,但它们不一样。 更糟糕的是,你可以通过调用你喜欢的function来改变整个游戏。

Phew ……难道现在像c#这样的托管代码如此热门?

编辑: 我还应该补充一点,你可以做*a_p = X ,但尝试用你的一个数组做到这一点! 数组与指针一样使用内存,但它们无法移动或resize。 *a_p这样的指针可以指向不同的东西。

为了简单解释一下,我会把帽子扔进戒指:

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

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

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

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

  • 可以在期望指针参数的位置传递数组,并且它将自动转换为指向第一个元素的指针(尽管这对于多个指针级别或多维数组不是递归的)。 再次,这是设计的。

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

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

如果你有一个指向字符数组的指针(并希望获得该数组的大小),你不能使用sizeof(ptr),而是必须使用strlen(ptr)+1!