复合字面量(compound literals)详解

本文探讨了C99标准中的复合字面量,如何用于表示数组和结构体,并介绍了其定义语法、语义、不同作用域下的匿名数组实例。复合字面量提供了静态或自动存储的对象,适用于文件和函数作用域,并揭示了单例复合字面量在函数内部的独特性质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字面量是除了符号常量之外的常量。如,1是int型字面量,3.14是float型字面量,'C’是char型字面量,'Yudao’是字符串字面量。那么,数组和结构体是否也能有字面量来表示呢?如定义了这种"字面量",能带来什么好处呢(后文中逐一介绍)? 因此,C99标准委员会就新增了复合字面量(compound literals)

定义

语法

( type-name ) { initializer-list }

( type-name ) { initializer-list , }

约束

  1. The type name shall specify an object type or an array of unknown size, but not a variable length array type.

type name指定了数组类型或结构体类型,数组长度不能是可变的。

  1. No initializer shall attempt to provide a value for an object not contained within the entire unnamed object specified by the compound literal.

匿名"对象"的初始化必须在在复合字面量的大括号中。

  1. If the compound literal occurs outside the body of a function, the initializer list shall consist of constant expressions.

如果复合字面量是文件作用域,initializer list的表达式必须是常量表达式。

语义

  1. 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指定。

  2. 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的元素个数决定。复合字面量可以作为左值。

  3. 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。

  4. 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},使代码更加的简洁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值