为什么在以下结构定义中需要显式强制转换

Why is an explicit cast necessary in the following struct definition

struct 通过复合文字初始化,它将自己进行转换。例如:

struct movie {
    char title[50];
    int year;
};
typedef struct movie Item;

typedef struct node {
    Item        item;
    struct node *next;
} Node;

typedef struct linkedlist {
    Node   *head;
    size_t size;
} LinkedList;
LinkedList movies2 = {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
};

但是,如果我分开定义,我必须添加一个显式转换:

LinkedList movies2;
movies2 = (LinkedList) {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
};

代码:https://godbolt.org/z/dG8nMh

如果我在第二个中遗漏了 (cast_type),我将得到类似 error: expected expression before ‘{’ token 的错误。为什么会这样?

也就是说,为什么初始化不需要强制转换而另一个定义需要?我的想法是第二个版本应该能够在没有显式转换的情况下自行解析,但显然这是不正确的。

因为在第一个片段中,您正在使用这些值初始化您的结构。 但是在你的第二个片段中,你正在创建一个 compound literal 然后将它复制到你的结构中。

在C语言中,左值和右值应该是同一类型。在表达式

movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1 };

您正在确保右值是 LinkedList 类型。如果删除 (LinkedList),则右值的类型未知。这就是错误的原因。

在这两种情况下,您都使用复合文字。

声明中的第一种情况

LinkedList movies2 = {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
};

您正在初始化列表中使用 Node 类型的复合文字 (Node){{"Avatar", 2010}, NULL},其地址用作结构 head 的数据成员 head 的初始化器=20=].

在第二种情况下,您首先创建了一个 LimkedList

类型的对象
LinkedList movies2;

然后您使用赋值运算符对创建的对象使用 LinkedList

类型的复合文字
(LinkedList) {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
}

movies2 = (LinkedList) {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
};

那是没有任何铸造。使用了两种不同的复合文字。一种是 Node 类型,另一种是 LinkedList 类型。

说清楚。考虑一个简单的例子

int x = { 10 };

在上面的声明中,变量 x 由整数常量 10 初始化。

你也可以这样写

int tmp = { 10 };
int x;
x = tmp;

这里创建了一个中间变量来初始化变量x。复合文字实际上是一个未命名的对象。上面的代码可以改写成

int x;
x = ( int ){ 10 }; 

这里没有任何铸造。这个表达式 ( int ){ 10 } 创建了一个 int 类型的未命名对象,它由整数常量 10 初始化。这个新创建的未命名对象被分配给变量 x.

另请参阅以下问题

似乎是 显式转换 是复合文字语义学 (type_name){ }.

的一部分

复合文字的语法可能会与类型转换混淆,但是转换是非左值表达式,而复合文字是左值。

C11 N1570 §6.5.2.5

Semantics

  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.

第一个是初始化器,不是赋值,所以不需要它。


我还要注意,在表达式 .head = &(Node){{"Avatar", 2010}, NULL} 中,复合文字是一个左值,可以通过地址获取,但它的生命周期仅限于它所属的范围,在该范围之外访问它会调用 undefined行为,

LinkedList getLinkedList()
{
    LinkedList movies2;
    movies2 = (LinkedList){
        .head = &(Node){{"Avatar", 2010}, NULL},
        .size = 1};       
    return movies2;
}
LinkedList l = getLinkedList();
printf("%s", l.head->item.title); // undefined behavior

其他答案涵盖了复合文字的初始化和赋值之间的区别。然而,还有另一个概念点需要提出:

LinkedList movies2 = {
    .head=&(Node){{"Avatar", 2010}, NULL},
    .size=1
};

任一情况下,您正在创建一个链表并将头部设置为指向一个节点,该节点不是动态分配的,也不是某个应用程序定义的池的一部分。链表的成员通常通过 malloc 动态创建或从池中获取。

您现在拥有的是列表中的一个“特殊”节点,因为它没有像添加其他节点那样添加到列表中,这意味着您需要额外的逻辑来跟踪如果它被删除,它的处理方式与列表中的其他节点不同。

最好将列表的头部初始化为NULL。然后你的添加/删除功能需要知道列表是否为空。