我了解到:
复合字面量是C99的一个特性,可以用于创建没有名称的数组。考虑以下示例:
int *p = (int []){3, 0, 3, 4, 1};
p
指向一个包含3, 0, 3, 4
和1
的五个元素数组的第一个元素。
实际上,我想知道,由于这个数组没有名称,它是否会存储在内存中?
换句话说,在以下情况下:
char* str = "hello"
字符串 "hello"
将存储在内存中的哪里?
我了解到:
复合字面量是C99的一个特性,可以用于创建没有名称的数组。考虑以下示例:
int *p = (int []){3, 0, 3, 4, 1};
p
指向一个包含3, 0, 3, 4
和1
的五个元素数组的第一个元素。
实际上,我想知道,由于这个数组没有名称,它是否会存储在内存中?
换句话说,在以下情况下:
char* str = "hello"
字符串 "hello"
将存储在内存中的哪里?
p[0], p[1], ...
或者
*p, *(p + 1), ...
以下是需要翻译的内容:
事实上,C语言中有很好的原始类型字面量,如int
和char
,甚至还有字符串字面量。因此,我们可以很容易地表达出这样的东西:
int length(char *s);
int len = length("Hello, World!");
int sum(int a[], int n);
int total = sum((int []){ 17, 42 }, 2);
C 2011 (N1570) 6.5.2.5 5规定:
如果复合字面量出现在函数体的外部,则对象具有静态存储期;否则,它具有与包含块相关联的自动存储期。
因此,您展示的复合字面量具有自动存储期。C标准没有指定这些对象在内存中的位置。将内存组织起来是C实现的工作。通常情况下,具有自动存储期的对象是在堆栈上创建的,但是实现可能通过其他方式管理这些对象。
特别地,假设您在其他地方记录了 p
的值并递归调用包含此复合字面量的程序。当程序再次初始化 p
时,将存在第二个复合字面量实例。它们实际上是不同的对象,并且C标准要求它们的地址不同。因此,如果打印这两个指针,它们将具有不同的值。但是,如果优化器能够确定您不会这样做,并且永远不会比较指向不同复合字面量实例的两个指针,并且没有其他可以区分它们的可观察行为(由C标准定义),则C实现可以自由使用一个实际的复合字面量实例,而不是每次都创建新实例。在这种情况下,编译器可以将复合字面量保留在数据节中,而不是在堆栈上。
以下是演示两个相同复合字面量实例具有不同地址的代码:
#include <math.h>
#include <stdio.h>
void foo(int *q)
{
int *p = (int []) { 2, 3 };
if (!q)
foo(p);
else
printf("p = %p, q = %p.\n", (void *) p, (void *) q);
}
int main(void)
{
foo(0);
return 0;
}
字符串字面量是不同的。C 2011(N1570)6.4.5 6说:
在第7个翻译阶段,对于由一个或多个字符串字面量产生的多字节字符序列,将追加值为零的字节或代码。78)然后使用多字节字符序列来初始化具有静态存储期和长度的数组,该数组刚好足够包含该序列。
因此,字符串字面量表示具有静态存储期的对象。即使递归调用包含它的函数,也只有一个实例。
int *p = (int []){3, 0, 3, 4, 1};
,然后尝试修改数组 p[2] = 34;
,这可能吗?我相信这会导致分段错误,因为我正在尝试更改常量数组。 - Grijesh Chauhanchar* s = "hello";
和 char* s = (char []){'h', 'e', 'l', 'l', 'o', '\0'};
有很大的不同吗? - Grijesh Chauhanchar *s = (char []){"hello"};
。 - Eric Postpischilchar str[3] = "abc";
等同于char str[3] = {'a','b','c'};
。在C语言中,这种用法是被禁止的。个人认为应该有一个定义好的前缀来表示字符串字面值不需要终止的空字节,因为有些情况下字符串的长度会以其他方式知道,而空字节最多只会浪费空间[如果字符串是结构体的一部分,则可能没有空间用于空字节]。 - supercat就像在C语言中将字符串赋值给char指针一样,您同样可以将一个整数数组赋值给类型为int*的p
:
当您声明:int *p = (int []){3, 0, 3, 4, 1};
时,可以假设它被存储在内存中,类似于:
p 23 27 31 35 36 37
+----+ +----+----+----+----+----+----+
| 23 | | 3 | 0 | 3 | 4 | 1 | ? |
+----+ +----+----+----+----+----+----+
▲ ▲ ▲ ▲ ▲ ▲
| | | | | // garbage value
p p+1 p+2 p+3 p+4
p[0] == 3
p[1] == 0
p[2] == 3
p[3] == 4
p[4] == 1
注意:
我们使用char* str = "hello";
来表示字符串。但是在C语言中,字符串字面值的类型是char[N]
而不是char*
。但是在大多数表达式中,char[N]
可以转化为char*
。
要点:
当你声明一个数组时,例如:
int p[] = {3, 0, 3, 4, 1};
那么这里的 p
类型是 int[5]
,&p
是一个指向数组的指针,类型是:int(*)[5]
而在声明中:
int *p = (int []){3, 0, 3, 4, 1};
p
指向第一个元素,类型为int*
,&p
的类型为int**
。(这类似于将字符串分配给char指针)。
在第一种情况下,对于0 <= i <= 4
,p[i] = 10;
是合法的。但在第二种情况下,你正在写入只读内存-非法内存操作-段错误。
要点:
以下声明也是有效的:
int *p = (int *){3, 0, 3, 4, 1};
问:实际上我想知道,由于这个数组没有名称,它会被存储在内存中吗?
当然,数组是存储在内存中的,但它只是由p
指向(p
不是数组的名称),没有其他名称与之对应。例如,如果您执行以下操作:
int *p = (int []){3, 0, 3, 4, 1};
int i = 10;
p = &i;
现在您已经丢失了数组的地址,这与以下情况非常相似:
char* s = "hello";
char c = 'A';
s = &c;
现在你失去了"hello"
的地址。
常量的内存来自静态段,在声明时分配。任何int数组或char字符串字面值都会被存储在那里。当您的声明运行时,地址被分配给指针变量。但是常量没有名称,只有值。在这两种情况下,数组和字符串"hello"
成为可执行文件中数据部分的一部分(您可以反汇编以查找其中的值)。
两种方法:
&((int[]){3,0,3,4,1})[3]
并且
((int[]){3,0,3,4,1})+3
请注意,如果字面值在函数内部,则当退出封闭块时,对其的指针将无效。
int *p
的指针...(注意:原文中的 "just" 意为 "刚刚",不是只有的意思。) - user529758malloc()
返回的数组也没有名称。 - Barmarchar * p = "some words";
中的字符串字面量也没有“名称”,但是你会认为这个字符串不是“存储在内存中”的吗? - Crowmangoto
或longjmp
这样的跳转。 - Jens Gustedt