字面量是除了符号常量之外的常量。如,1是int型字面量,3.14是float型字面量,'C’是char型字面量,'Yudao’是字符串字面量。那么,数组和结构体是否也能有字面量来表示呢?如定义了这种"字面量",能带来什么好处呢(后文中逐一介绍)? 因此,C99标准委员会就新增了复合字面量(compound literals)。
定义
语法
( type-name ) { initializer-list }
( type-name ) { initializer-list , }
约束
- The type name shall specify an object type or an array of unknown size, but not a variable length array type.
type name指定了数组类型或结构体类型,数组长度不能是可变的。
- No initializer shall attempt to provide a value for an object not contained within the entire unnamed object specified by the compound literal.
匿名"对象"的初始化必须在在复合字面量的大括号中。
- If the compound literal occurs outside the body of a function, the initializer list shall consist of constant expressions.
如果复合字面量是文件作用域,initializer list的表达式必须是常量表达式。
语义
A postfix expression that consists of a parenthesized type name followed by a brace enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.
一个由
( type-name ) { initializer-list }
构成的后缀表达式就是复合字面量
,它定义了一个匿名"对象",该"对象"的值由initializer list指定。If the type name specifies an array of unknown size, the size is determined by the initializer list as specified in 6.7.8, and the type of the compound literal is that of the completed array type. Otherwise (when the type name specifies an object type), the type of the compound literal is that specified by the type name. In either case, the result is an lvalue.
如果type name是一个匿名数组,那么该数组的长度由initializer list的元素个数决定。复合字面量可以作为左值。
The value of the compound literal is that of an unnamed object initialized by the initializer list. If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.
复合字面量的值是由initializer list指定。如果复合字面量是文件作用域,该"对象"是static storage duration;如果复合字面量是函数作用域或块作用域,该"对象"是automatic storage duration。
All the semantic rules and constraints for initializer lists in 6.7.8 are applicable to
compound literals.
用法
文件作用域的匿名数组
#include <stdio.h>
#include <stdlib.h>
int *p = (int[]){1, 2, 3};
int *p2 = (int[]){1, 2, 3};
int main(int argc, char** argv)
{
printf("p=%p\n", p);
printf("p2=%p\n", p2);
return 0;
}
// 运行结果为
// p=0x55764bdc4010
// p2=0x55764bdc4020
文件作用域(file scope)定义复合字面量,initializer-list
中的表达式必须为常量。文件作用域的复合字面量,具有static storage duration,存储在.data section
。
函数作用域的匿名数组
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int a[32] = {4,5,6};
int *p = (int[]){1, 2, 3};
printf("&a[0]=%p\n", &a[0]);
printf("p=%p\n", p);
return 0;
}
// 运行结果
// &a[0]=0x7ffff11d2fb0
// p=0x7ffff11d2fa4
函数作用域的复合字面量,具有automatic storage duration,从打印的地址可以知道,匿名数组存储在栈上。(栈特点:向低地址方向增长)
"/tmp/fileXXXXXX" // static storage duration;数组类型为char;数组内容不能修改
(char []){"/tmp/fileXXXXXX"} // 在函数作用域内时,automatic storage duration,该匿名数组的内容可以修改
(const char []){"/tmp/fileXXXXXX"} // 在函数作用域内时,automatic storage duration,因为有const修饰,该匿名数组的内容不可以修改
// 与字符串字面量(如,"abc")类似,const修饰的复合字面量也可是read-only memory和can even be shared
(const char []){"abc"} == "abc"
// 如果字面量的storage是shared,该表达式为真。
// storage is shared该怎么理解?
同一函数作用域的单例复合字面量"对象"
为了讲清楚什么是 单例 复合字面量"对象",先看如下代码
#include <stdio.h>
#include <stdlib.h>
struct s { int i; };
// 函数f的返回值总是为1
int f (void)
{
struct s *p = 0, *q;
int j = 0;
again:
q = p, p = &((struct s){ j++ });
printf("p=%p\n", p);
if (j < 2) goto again;
return p == q && q->i == 1;
}
// 函数f2的返回值也总是为1
int f2 (void)
{
struct s *p = 0, *q;
int j = 0;
while(j < 2)
{
q = p, p = &((struct s){ j++ });
printf("p=%p\n", p);
}
return p == q && q->i == 1;
}
int main(int argc, char** argv)
{
printf("f() return:%d\n", f());
printf("f() return:%d\n", f());
printf("f() return:%d\n", f());
printf("f2() return:%d\n", f2());
printf("f2() return:%d\n", f2());
printf("f2() return:%d\n", f2());
return 0;
}
看一下,运行结果:
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f() return:1
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f() return:1
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f() return:1
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f2() return:1
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f2() return:1
p=0x7fff543fa7f0
p=0x7fff543fa7f0
f2() return:1
从打印结果可以看出,函数f和函数f2中的指针p指向的匿名对象的地址都相同。
在函数f
的作用域内,复合字面量"对象"是单例;
在函数f2
中,虽然匿名对象的地址也相同,但由于有while的块作用域,不一定匿名对象是单例,也可能恰好第一次循环分配的匿名对象释放了,然后第二次循环又在相同的地址上分配该匿名对象。
在C99标准中有这样一段话:
Each compound literal creates only a single object in a given scope.
Note that if an iteration statement were used instead of an explicit goto and a labeled statement, the lifetime of the unnamed object would be the body of the loop only, and on entry next time around p would have an indeterminate value, which would result in undefined behavior.
我暂时没有找到详细关于这段话的解释,只能通过实际的测试来验证,可能我上述的结论不一定正确。
接下来,看一个实际工程中使用复合字面量的例子
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
#define AV_FOURCC_MAX_STRING_SIZE 32
#define av_fourcc2str(fourcc) av_fourcc_make_string((char[AV_FOURCC_MAX_STRING_SIZE]){0}, fourcc)
char *av_fourcc_make_string(char *buf, uint32_t fourcc)
{
int i;
char *orig_buf = buf;
size_t buf_size = AV_FOURCC_MAX_STRING_SIZE;
for (i = 0; i < 4; i++) {
const int c = fourcc & 0xff;
const int print_chr = (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c && strchr(". -_", c));
const int len = snprintf(buf, buf_size, print_chr ? "%c" : "[%d]", c);
if (len < 0)
break;
buf += len;
buf_size = buf_size > len ? buf_size - len : 0;
fourcc >>= 8;
}
return orig_buf;
}
int main(int argc, char** argv)
{
uint32_t type = MKTAG('r', 'o', 'o', 't');
printf("%s\n", av_fourcc2str(type));
return 0;
}
av_fourcc2str
宏的作用是将以整型存储的4字符转换为字符串,其中使用了(char[AV_FOURCC_MAX_STRING_SIZE]){0}
,使代码更加的简洁。