C编译器如何知道char ** x指向一个数组?

在C中,我读过char** xchar* x[]含义完全相同。 我理解第二个例子,但我不明白第一个例子。

对我来说,第一个例子是“指向指向字符的指针”,但C会将其视为“指向字符数组的指针列表”?

有人能够以外行的方式向我解释这一点,因为我一直有一个exception困难的时间把握它。

编辑:我承认我说的第一点是我认为人们想要听到的而不是我的实际解释。 我也把它看作是指向char的指针,但是当我在这个问题上读到最佳答案时,我的理解就下降了: char * argv []和char ** argv之间的区别是main()的第二个参数

这表明当用作参数向量时,它被用作指向字符指针数组的指针…

长话短说 – 它没有区分。 编译器不知道它指向一个数组。 它看到的只是一个指针。 您显示的方式 (作为函数参数声明的一部分 – 传递给函数) char *[]char **表示相同的事情。 这是什么? 指向char*指针。 没有比这更好的了。

编译器没有保留额外的信息,这使得它区分你是否传递了char*数组或char**因为最终char*数组衰减成指向第一个元素的指针 – 这是char**

试试这个,你会明白: –

 char c = 'a'; char *pc = &c; char **ppc = &pc; f(ppc); 

….

 void f(char *ppc[]){ } 

这不会让编译器抱怨。 因为它最终被编译器视为char **ppc

为了certificate编译器在传递给函数时没有看到数组 – 你可以看到这个引用(基本上说它有衰减,它归结为指向第一个元素的指针。所以它看到一个指针就是全部)(这绝不会让人觉得char**char*[]所有情况下都是相同的,因为腐烂 – 它不是)

§6.3.2.1¶3c11标准N1570

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

编辑是为了清除误解,让人不理解答案。

C编译器如何知道char ** x指向一个数组?

它没有!

对我来说,第一个例子是“指向一个字符的指针”

正确。

但是C会将此视为“指向字符数组指针列表”吗?

也正确。

诀窍在于虽然语言不知道列表有多长,但只包含一个内容的列表仍然是一个列表。 所以这并不重要。 1

给定一个指针T* ptr*ptr将为您提供ptr指向的一个对象(一个),或者它将为您提供ptr指向的第一个对象(多于一个)。

只有当你开始递增那个指针(例如ptr++ ,或者像ptr[152]这样的访问期间)时,才知道实际上有多少个对象在另一端。 尽管如此,这对语言来说并不重要,因为这需要程序员才能做到正确。

1.确实存在指向数组的指针类型,因此可以将类型系统引入讨论,然后它就很重要。 但是我们只讨论指针到单个对象的类型(尽管链接到的答案使得“按值传递”数组的主题变得非常混乱)。


如果它有帮助,那么关于您链接到的问题的最佳答案的开场陈述:

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

…是错的。 char* argv[]作为char** argv透明地被驱逐(非常感谢,C!)所以它们都意味着完全相同的东西。

你应该阅读一个问题下的所有答案,而不只是一个; bmargulies更准确。

首先, char **xchar *x[]不等效,除了函数参数声明。

当量:

 int main(int argc, char **argv); int main(int argc, char *argv[]); // equivalent 

不等同于:

 { char **pointer; char *array[3]; } 

AC T *指针可以指向T类型的单个对象,或者它可以指向“ T数组”的任何元素,并且还可以指向一个元素超出此类数组的末尾。

此信息不包含在指针的语言级别,程序员可见类型信息中。

在某些情况下,编译器可以推断出这些情况,并在出现错误时提供有用的诊断。

编译器可以跟踪表达式值来自的数据流。

例如,给定:

 char *array[3]; /* array of 3 "char *" pointers */ char **ptr = array; /* pointer to array[0] */ 

在这里,编译器可以分析ptr是从array初始化的。 因此,在初始化和程序中的任何其他点之间,如果没有对ptr进行赋值,则可以假设它仍然指向array[0]

因此,编译器可能会为ptr + 5等可疑表达式生成警告,同时对ptr + 3保持静默。

但是,ISO C不要求任何此类分析或诊断; 这是“实施质量”的问题。

静态诊断有多少是有限的。 C函数可以接收指针值作为“无处不在”的参数,在编译时无法合理地知道这些参数。


顺便说一下,声明风格char* ptr是一个坏习惯; 它应该被设计为char *ptr 。 请注意, char* a, b声明为指针char* a, b声明为char 。 语法是char是“声明说明符”的列表。 这些说明符后面是逗号分隔的“声明符”列表。 这些声明符分别是*ab 。 如果你写char* ptr ,你会在声明符的中间放一个空格分区,同时将它与说明符一起聚集在一起。

这有点像取算术表达式x + x*y + y并将其写为x+x * y+y ,因此看起来像乘法最后完成。

在编程中避免这种误导性的空白。

为了简化说明,让我们用TypeName替换char* ,它可以是任何东西。

当它们用作函数参数时, TypeName* xTypeName x[]完全相同。

一些使用第一种forms来指示指向单个对象的指针,第二种forms用于指示指向数组的第一个元素的指针。 从编译器的角度来看,它们完全相同。

假设你有一个变量:

 TypeName a[10]; 

a在函数调用中用作参数时,衰减到指向第一个alement的指针

  foo(a); 

是相同的:

 foo(&a[0]); 

在函数中,参数可以声明为TypeName* xTypeName x[]

无论参数如何声明,您都可以使用数组语法x[i]访问数组的元素。

 void foo(TypeName* x) { x[0] = ; } 

 void foo(TypeName x[]) { x[0] = ; } 

是一样的。 它们为数组的第一个元素赋值。

在函数参数声明的上下文中, T a[N]T a[]被解释为T *a – 所有三个都声明a作为指向T的指针:

 void foo( T a[N] ) 

是相同的

 void foo( T a[] ) 

是相同的

 void foo( T *a ) 

所以,用char *替换T ,你得到

 void foo( char *arr[N] ) 

是相同的

 void foo( char *arr[] ) 

是相同的

 void foo( char **arr ) 

适用于函数参数声明 – 在常规声明中,

 T a[N]; 

一样的

 T *a; 

那么为什么会这样呢?

除非它是sizeof或一元&运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,否则“N元素数组T ”的表达式将被转换(“衰减”)为表达式“指向T指针”,表达式的值将是数组中第一个元素的地址。

所以,如果你声明一个数组

 T a[N]; 

并将其传递给函数as

 foo( a ); 

函数调用中的表达式 a从“N的N元素数组”转换为“指向T指针”,函数foo实际接收的是指针值,而不是数组:

 void foo( T *a ) { ... } 

使用a[]代替*a是B编程语言的延续,从中导出了C语言。 很坦率地说,很多Carrays的怪异可以追溯到B. 就个人而言,我认为它造成了更多的混乱而不是它的价值,但我不是在编写标准的委员会。

你有时会听到有人宣称“数组只是一个指针” – 这是对规则的误解。 数组和指针不相同,但在大多数情况下,数组表达式将转换为指针表达式。