【问题标题】:How to correctly initialise a struct in C如何正确初始化 C 中的结构
【发布时间】:2021-09-22 10:44:32
【问题描述】:

我正在使用 C 语言开发一个小项目,我想在函数中分配结构并将它们添加到结构数组中。

由于某种原因,当我打印结构数组的内容时,我似乎开始从未分配的内存中打印。

下面提供了一个最小的工作示例:

#include <stdio.h>
#include <stdlib.h>

// This struct simply stores a list and its size
struct list {
    int* values;
    size_t size;
};

// This function initialises an array of lists
// of size len, which each list (i.e. list.values)
// having a size of list_len
// `lists` is an array of pointers to lists
void list_init(struct list** lists, size_t len, size_t list_len) {
    for (size_t i = 0; i < len; i++) {
        struct list list;
        list.values = malloc(sizeof(int) * list_len);
        list.size = list_len;
        lists[i] = &list;
    }
}

void main() {
    int len = 3;
    struct list* lists[len];
    list_init(lists, len, 5);

    // Print lists
    for (size_t i = 0; i < len; i++) {
        printf("list %zu: ", i);
        printf("size: %zu\n", lists[i]->size);
        for (size_t j = 0; j < 5; j++) { // Using 5 instead of lists[i]->size for obvious reasons
            printf("%d ", lists[i]->values[j]);
        }
        printf("\n");
    }
}

我期望的输出是:

list 0: size: 5
0 0 0 0 0
list 1: size: 5
0 0 0 0 0
list 2: size: 5
0 0 0 0 0

但我得到了:

list 0: size: 5
0 0 0 0 0 
list 1: size: 140727488332736
0 0 0 0 0 
list 2: size: 140727488332736
0 0 0 0 0

这是一个非常明显的信号,表明我正在访问我不应该访问的内存。

我注意到,如果我通过将内存分配给带有struct list* list = malloc(sizeof(struct list)); 的指针来声明列表,程序会给出预期的输出,而不是用struct list list; 声明列表。这是为什么?如果我想创建对象而不是指针,我该如何正确地做到这一点。


P.S.:我知道我可以将 list 初始化为指针。这个问题主要是问为什么不能我把它初始化为一个对象

【问题讨论】:

  • 函数结束时list_init中的本地struct list list消失了(它是一个局部变量!)。然后存储在数组中的指针悬空。旨在保留函数返回的数据必须是全局的(或函数静态的)或动态分配的(典型情况,然后打开所有权的蠕虫罐)。解决这个问题的一种方法是在 main 中定义 lists 不是指针列表,而是对象列表。您仍然可以在单独的函数中初始化它们(如果您对通过 struct list lists[len] = {}; 进行的值初始化不满意)。
  • 顺便说一句,我建议对类型和变量使用不同的名称。是的,结构标记存在于与 C 中的其他标识符分开的命名空间中,但它仍然不利于可理解性。我也不确定当您将代码移植到 C++ 并希望在新代码中省略冗余关键字 struct 时会发生什么(过渡到 C++ 始终是未来的可能性,不应轻易浪费)。
  • @Peter-ReinstateMonica 这很有意义。可以肯定的是,这是否意味着我可以通过两种方式来实现它?我可以将lists 设为struct list 的数组并使用struct list list;struct list 指针的数组和malloc 函数中的内存来初始化元素。 (另外,你对变量/类型命名是正确的。当我校对我的问题时,我有点羞愧;-))
  • 是的,正确的。如果你有一个列表数组,你可以直接写入数组元素的成员,不再需要本地结构。

标签: arrays c struct


【解决方案1】:

您保存对同一个局部变量的引用,它是一个 UB。分配的内存也会丢失。你的 main 也错了。

我会这样做(calloc 用作 main 你打印未初始化的分配内存):

typedef struct list {
    size_t size;
    int values[];
}list;

list **list_init(list **array, size_t size, size_t list_len) 
{
    list **wrk;
    if(!array) wrk = malloc(sizeof(*wrk) * size);
    else wrk = array; 
    if(wrk)
        for (size_t i = 0; i < size; i++) {
            list *list = calloc(1, sizeof(*list) + list_len * sizeof(list -> values[0]));
            /* check for allocation errors!!!! */
            list -> size  = list_len;
            wrk[i] = list;
        }
    return wrk;
}

int main(void) {
    size_t len = 3;
    list **lists;
    
    /* if you pass NULL it will create the list of lists itself */
    lists = list_init(NULL, len, 5);
    /* check for allocation errors!!!! */

    // Print lists
    for (size_t i = 0; i < len; i++) {
        printf("list %zu: ", i);
        printf("size: %zu\n", lists[i]->size);
        for (size_t j = 0; j < 5; j++) { // Using 5 instead of lists[i]->size for obvious reasons
            printf("%d ", lists[i]->values[j]);
        }
        printf("\n");
    }
    for (size_t i = 0; i < len; i++) free(lists[i]);
    free(lists);
}

https://godbolt.org/z/9TPe1sM1a

【讨论】:

    【解决方案2】:

    函数list_init中的这些语句

    struct list list;
    //...
    lists[i] = &list;
    

    没有意义,因为退出函数后本地对象列表将不再存在。所以你会有一个struct list *类型的无效指针数组。

    您需要动态分配将由数组元素指向的struct list 类型的每个对象。

    函数可以通过以下方式声明和定义,例如

    size_t list_init( struct list **lists, size_t len, size_t list_len ) 
    {
        size_t count = 0;
    
        for ( size_t i = 0; i < len; i++ ) 
        {
            lists[i] = malloc( sizeof( struct list ) );
            
            if ( lists[i] != NULL )
            {
                ++count;
                
                lists[i]->size = 0;
    
                lists[i]->values = malloc( sizeof( int ) * list_len );
                
                if ( lists[i]->values != NULL ) lists[i]->size = list_len;
            }
        }
    
        return count;
    }
    

    同样由于函数没有初始化数据成员值指向的分配数组,所以这个循环在 main 中

        for (size_t j = 0; j < 5; j++) { // Using 5 instead of lists[i]->size for obvious reasons
            printf("%d ", lists[i]->values[j]);
        }
    

    将调用未定义的行为。

    您可以通过使用 calloc 而不是 malloc 来对 arras 进行零初始化。例如

    lists[i]->values = calloc( list_len, sizeof( int ) );
    

    请注意,根据 C 标准,不带参数的函数 main 应声明为

    int main( void )
    

    这是您的更新程序。

    #include <stdio.h>
    #include <stdlib.h>
    
    // This struct simply stores a list and its size
    struct list 
    {
        int* values;
        size_t size;
    };
    
    size_t list_init( struct list **lists, size_t len, size_t list_len ) 
    {
        size_t count = 0;
    
        for ( size_t i = 0; i < len; i++ ) 
        {
            lists[i] = malloc( sizeof( struct list ) );
            
            if ( lists[i] != NULL )
            {
                ++count;
                
                lists[i]->size = 0;
    
                lists[i]->values = calloc( list_len, sizeof( int ));
                
                if ( lists[i]->values != NULL ) lists[i]->size = list_len;
            }
        }
    
        return count;
    }
    
    
    int main(void) 
    {
        size_t len = 3;
        struct list* lists[len];
        
        list_init(lists, len, 5);
    
        // Print lists
        for ( size_t i = 0; i < len; i++ ) 
        {
            printf( "list %zu: ", i );
            
            if ( lists[i] != NULL )
            {
                printf( "size: %zu\n", lists[i]->size );
                for (size_t j = 0; j < lists[i]->size; j++) 
                {
                    printf( "%d ", lists[i]->values[j] );
                }
                
                printf("\n");
            }           
        }
        
        return 0;
    }
    

    程序输出是

    list 0: size: 5
    0 0 0 0 0 
    list 1: size: 5
    0 0 0 0 0 
    list 2: size: 5
    0 0 0 0 0 
    

    当然,您需要在程序中添加代码,以释放函数list_init 中分配的所有内存。

    【讨论】:

      猜你喜欢
      • 2019-02-20
      • 2011-08-20
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      • 2017-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多