【问题标题】:Why is an explicit cast necessary in the following struct definition为什么在以下结构定义中需要显式强制转换
【发布时间】:2021-06-08 19:37:22
【问题描述】:

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 的错误。为什么会这样?

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

【问题讨论】:

  • 这将有助于显示这些东西的定义。
  • 我认为这是未定义的行为。您正在使用临时地址,一旦超出范围就会被丢弃。演员表是“必要的”,因为默认情况下它不想工作。
  • 您可以将数组分配给结构变量类型吗?
  • @tadman 更新了结构。
  • (LinkedList) {} 不是演员表。它是复合文字的语法,您似乎知道。

标签: c struct initialization literals compound-literals


【解决方案1】:

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

声明中的第一种情况

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

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

在第二种情况下,您首先创建了一个 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

另见以下问题What are the advantages of using “{}” for casting in C Language?

【讨论】:

  • 对,但是为什么{.head=&(Node){{"Avatar", 2010}, NULL}, .size=1}; 会失败(没有明确的演员表)?
  • @David542 没有任何演员表。使用了两种不同的复合文字。
【解决方案2】:

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

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

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

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

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

【讨论】:

    【解决方案3】:

    看起来是显式转换的东西是复合文字语义(type_name){ }的一部分。

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

    C11 N1570 §6.5.2.5

    语义

    1. 由带括号的类型名称后跟用大括号括起来的初始化器列表组成的后缀表达式是复合文字。它提供了一个未命名的对象,其值由初始化列表给出。

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


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

    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
    

    【讨论】:

    • 我不会叫它typecast
    • @EugeneSh.,是的,但是我应该怎么称呼它,一个带括号的类型名称? :)
    • “你在问题中提到的(type_name) 是“显式转换(它不是)”` :)
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-06
        • 1970-01-01
        • 2014-03-16
        相关资源
        最近更新 更多