C语言复合字面量,指向数组的指针

33

我试图将复合字面量赋值给一个变量,但似乎没有成功,看:

  int *p[] = (int *[]) {{1,2,3},{4,5,6}};

我在gcc中遇到了一个错误。

但如果我只写这个:

  int p[] = (int []) {1,2,3,4,5,6};

那就没问题了。

但这不是我想要的。

我不明白为什么会出现这个错误,因为如果我像初始化数组一样初始化它,或者使用一个字符数组的指针,它就没问题,看:

  int *p[] = (int *[]) {{1,2,3},{4,5,6}}; //I got a error
  int p[][3] = {{1,2,3},{4,5,6}}; //it's okay
  char *p[] = (char *[]) {"one", "two"...}; // it's okay!

注意,我不明白为什么第一个会出现错误,请注意我不能或者说我不想写成第二种形式,因为它需要复合字面量,并且我不想告诉编译器数组的大小。我希望有像第二个例子一样的东西,但是针对int值。

提前致谢。


为什么需要在一个语句中初始化这些数组? - Judge Maygarden
如果我理解了这个问题,为了简化事情,例如我可以将va_args替换成复合字面量... - drigoSkalWalker
3
你多次提到你遇到了一个错误,但却没有告诉我们具体是什么错误。年轻的学徒,难道你一无所获吗? - abelenky
2
int p[] = (int []) {1,2,3,4,5,6}; 在 Linux 上使用 gcc 4.8.2 会出现错误。错误信息为“array initialized from non-constant array expression”。请问您的 gcc 版本是多少? - Albert Netymk
5个回答

32

首先,在你的所有示例中,强制类型转换都是多余的,可以删除。其次,你正在使用初始化多维数组的语法,这需要定义第二维以便分配一个连续的内存块。相反,尝试以下两种方法之一:

  • 多维数组:

  • int p[][3] = {{1,2,3},{4,5,6}};
    
  • 指向一维数组的指针数组:

  • int p1[] = {1,2,3};
    int p2[] = {4,5,6};
    int *p[] = {p1,p2};
    
    后一种方法的优点是允许子数组长度不同。而前一种方法则确保内存连续布局。
    另一个我强烈建议您不要使用的方法是将整数编码为字符串文字。这是一个非可移植的hack。此外,字符串文字中的数据应该是常量。你的数组需要可变吗?
    int *p[] = (int *[]) {
        "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00",
        "\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00"
    };
    

    那个示例在32位小端机器上可能有效,但我现在正在用iPad打字,无法验证它。请不要使用它,即使提到它,我也感觉很不好。

    你发现的强制转换方法似乎也适用于指向指针的指针。这也可以像多维数组一样索引。

    int **p = (int *[]) { (int[]) {1,2,3}, (int[]) {4,5,6} };
    

是的,在我的例子中它是多余的,但对于函数参数来说很有用,我如何避免指针的维度?就像第二个例子一样...感谢您的回答。 - drigoSkalWalker
您可以在字符串字面值中编码整数,但这样做不具备可移植性。 - Judge Maygarden
正确的写法是:int **p = (int *[]) {(int []){1,2,3},(int []){4,5,6}}; - drigoSkalWalker
是的,这似乎可行。它绝对比使用字符串字面量要好。 - Judge Maygarden
10
()中的类型并不是强制类型转换,而是复合字面量语法的一部分。 - u0b34a0f6ae
显示剩余5条评论

13
首先要理解“数组不是指针”
int p[] = (int []) {1,2,3,4,5,6};

在上述情况中,p 是一个整数数组。将元素 {1,2,3,4,5,6} 复制到 p 中。这里不需要进行类型转换,因为 rvaluelvalue 的类型都匹配,都是整数数组,所以不会出错。
int *p[] = (int *[]) {{1,2,3},{4,5,6}};

在上述情况下,p是一个整数指针数组。但是{{1,2,3},{4,5,6}}是一个二维数组(即[][]),不能强制转换为指针数组。您需要这样初始化 -
int p[][3] = { {1,2,3},{4,5,6} };
  // ^^ First index of array is optional because with each column having 3 elements
  // it is obvious that array has two rows which compiler can figure out.

但是,为什么这个语句编译通过了?
char *p[] = {"one", "two"...};

字符串字面值与整数字面值不同。在这种情况下,p也是一个字符指针数组。当实际上说了"one"时,它可以被复制到一个数组中,或者将其视为只读并指向其位置。
char cpy[] = "one" ;
cpy[0] = 't' ;  // Not a problem

char *readOnly = "one" ;
readOnly[0] = 't' ;  // Error because of copy of it is not made but pointing
                     // to a read only location.

使用字符串字面值时,上述任一情况均可。因此,该语句编译通过。但是 -

char *p[] = {"one", "two"...}; // All the string literals are stored in 
                               // read only locations and at each of the array index 
                               // stores the starting index of each string literal.

我不想告诉编译器数组有多大。

使用 malloc 动态分配内存是解决方案。

希望能帮到你!


你说{{1,2,3},{4,5,6}}是一个二维数组,不能强制转换为指针数组,但我学到的是任何数组都是指针,并且像指针一样传递到函数中。感谢你的回答!!! - drigoSkalWalker
如果数组是一个指针,那么尝试这样做 - void foo( int **twoDimensionalArray){} 并尝试像你的示例中提到的那样传递 p,它有良好的注释。 - Mahesh
3
仅仅因为一个一维数组可以使用指针的方式访问,并不意味着这个数组本身就是指针。 - Mahesh
1
数组和指针之间存在微妙的差异,但我认为除了初始化选项之外,在您的情况下不会有太大影响。 - Judge Maygarden
2
其中一个区别是你不能改变数组的地址,但你可以改变指针的地址。因此,你可以称数组为“静态地址”,指针为“动态地址”。 - Agnius Vasiliauskas
显示剩余2条评论

5

既然还没有人说过,如果你想要一个指向二维数组的指针,你可以(可能)这样做:

int (*p)[][3] = &(int[][3]) {{1,2,3},{4,5,6}};

编辑:或者你可以通过指向其第一个元素的指针来实现

int (*p)[3] = (int[][3]) {{1,2,3},{4,5,6}};

你的示例不起作用的原因是因为{{1,2,3},{4,5,6}}不是int*[]类型的有效初始化程序(因为{1,2,3}不是int*的有效初始化程序)。请注意,它不是int[2][3] - 它只是一个无效的表达式。
对于字符串的工作原因是因为"one"char[]char[N](其中N>3)的有效初始化程序。作为一个表达式,它大致等同于(const char[]){'o','n','e','\0'},除了编译器在失去常数性时不会有太多抱怨。

是的,初始化和表达式之间有很大的区别。 我相信在C99(可能是C++ pre-0x)中char s[] = (char[]){3,2,1,0};是编译错误。 还有许多其他事情,但T foo = ...;是变量初始化,而不是赋值,尽管它们看起来相似。 (在C ++中它们特别不同,因为不调用赋值运算符。)

指针混淆的原因:

  • 类型T []在必要时会隐式转换为类型T *(其第一个元素的指针)。
  • 函数参数列表中的T arg1 []实际上意味着T * arg1。 由于各种原因,您无法将数组传递给函数。 它是不可能的。 如果您尝试,则实际上正在传递指向数组的指针。 (但是,您可以将包含固定大小数组的结构传递给函数。)
  • 它们都可以使用相同的语义进行解引用和下标处理(我认为)。

编辑:细心的人可能会注意到,我的第一个示例大致上与int * p = &1;在语法上是等效的,但这是无效的。这在C99中可以工作,因为函数内部的复合字面量“具有与封闭块相关联的自动存储期限”(ISO/IEC 9899:TC3)。


1
你能否添加一些示例,其中T[]被隐式转换为T(*)[]?你的解释非常透彻!我认为你的答案真正看穿了复合字面量的迷雾,并指出了真正的问题! - Allan Ruin
1
经过一些测试,我认为应该是 int (*p)[3] = (int[][3]) {{1,2,3},{4,5,6}}; - Allan Ruin

1
你正在使用int指针数组,应该使用数组指针:
int (*p)[] = (int *) {{1,2,3}, {4,5,6}}

查看this的答案以获取更多细节。


1
我认为那甚至无法编译。 - tc.
1
@tc 首先,你应该先尝试这个答案 https://dev59.com/mXRA5IYBdhLWcg3wsgLq ,然后再发表评论。 - Karan
我仍然相信它无法编译。 - tc.
1
以下是代码: #include <stdio.h> void main() { int (*p)[] = (int *) {{1,2,3},{4,5,6}}; } - Karan
我错了;它似乎可以编译,但等同于 int (*p)[] = (int*)1;(“大括号中的标量初始化程序”,“初始化使指针从整数而来而不需要转换”,“标量初始化程序中有多余元素”,“与指针类型不兼容的初始化”,“'main' 的返回类型不是 'int'”)。我坚持我的反对票。 - tc.
@Karan 可能吧。但是你的例子是未定义的。换句话说,编译器可以自由地做任何它想做的事情,即使是停止并捕获火灾(https://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing))。`main()`必须返回一个int。这是不可谈判的。即使它在您的编译器中似乎工作正常,也是错误的。 - Pryftan

1

你似乎混淆了指针和数组。它们不是同一件事!数组本身就是列表,而指针只是一个地址。然后,使用指针算术运算,您可以假装指针是数组,并且由于数组的名称是指向第一个元素的指针,所以一切都变得混乱起来。 ;)

int *p[] = (int *[]) {{1,2,3},{4,5,6}};      //I got a error

这里,p是一个指针数组,所以您正在尝试将地址为1、2、3的元素分配给第一个数组,将4、5、6分配给第二个数组。段错误发生是因为您无法访问这些内存位置。

int p[][3] = {{1,2,3},{4,5,6}};              //it's okay

这是可以的,因为这是一个数组的数组,所以这一次1、2、3、4、5和6不是地址,而是它们自己的元素

char *p[] = (char *[]) {"one", "two"...};    // it's okay!

这是可以的,因为字符串字面量(“one”,“two”等)实际上不是字符串,而是指向这些字符串的指针,所以您正在将“one”字符串字面量的地址分配给p [1]。
顺便说一句,这与执行char abc []; abc =“abc”; 相同。这不会编译,因为您无法将指针分配给数组,而char *def; def =“def”; 解决了这个问题。


1
嗯,char abc[] = "abc"; 可以正常编译 -- 字符串字面值是 char 数组的有效初始化程序。 - Jim Balter

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