为什么在函数中创建的结构会被覆盖?

时间:2018-11-18 20:41:40

标签: c pointers struct

如何在函数中创建结构?我正在尝试这种方式:

#include <string.h>
#include <stdio.h>

typedef struct { char name[10]; } Item;

typedef struct { Item *item; } MyStruct;

MyStruct make(char *name) {
    MyStruct st;

    Item item;
    strcpy(item.name, name);
    st.item = &item;
    printf("name: %s\n", st.item->name);

    return st;
}

int main() {
    MyStruct s1, s2;
    s1 = make("hi");
    s2 = make("hey");

    printf("\nname: %s\n", s1.item->name);
    printf("name: %s\n", s2.item->name);
}

输出为:

name: hi
name: hey

name: hey
name: hey

创建s1时,结构s2被覆盖。每次调用该函数时,函数st上的结构make的地址看起来都是相同的。这是对的吗?如果可能的话,我想避免使用malloc

编辑:结果在macOS和Linux上有所不同,上面的输出来自Linux,下面的输出来自macOS。为什么不同?

name: hi
name: hey

name: p
name: p

3 个答案:

答案 0 :(得分:2)

函数返回后,MyStruct st;就被销毁,这是未定义的行为,因此您必须使用malloc并返回指向MyStruct的指针。 / strike>

实际上,返回的结构是perfectly fine,编译器将确保返回原始数据的副本,因此不会丢失任何数据。但是问题是,副本是 shallow ,因此,例如,指针是按原样复制的(原始指针指向的地址被复制,而不是数据被复制),这意味着里面的任何指针新复制的struct可能无效,因为它原来指向的内存位置可能在函数返回时破坏堆栈帧时已被释放。

您的函数使用内部带有指针的结构,并且该指针指向局部变量。后者(作为所有局部变量)在函数退出时被销毁,并且在返回原始结构的副本时,指针现在指向(可能)不再由程序控制的内存地址。

这就是为什么您的数据在第二次调用时被覆盖的原因。因此,如果要保留Item item,则应分配 it ,而不是函数的返回值(尽管您当然也可以这样做,但这不能解决由于item仍然会被销毁,因此该问题会在带有malloc的堆上出现。

答案 1 :(得分:2)

当您声明Item item;时,您是在本地分配它,它不在范围内。下一个函数调用会同时回收相同的内存位置。

这是未定义的行为,因为如果要在该范围之外持续存在,则需要动态分配。

答案 2 :(得分:1)

您的问题是误解了指针。让我们从一些非常相似的代码开始。

#include <string.h>
#include <stdio.h>

typedef struct { char name[10]; } Item;

typedef struct { Item item; } MyStruct; // <--- Removed the *

MyStruct make(char *name) {
    MyStruct st;

    Item item;
    strncpy(item.name, name, 10);  // <--- strncpy because name[] is a fixed length
    st.item = item;                // <-- Removed the &; *copies* item into st
    printf("name: %s\n", st.item.name); // <-- . rather than ->

    return st;
}

int main() {
    MyStruct s1, s2;
    s1 = make("hi");
    s2 = make("hey");

    printf("\nname: %s\n", s1.item.name); // <-- . rather than ->
    printf("name: %s\n", s2.item.name);   // <-- . rather than ->
}

在这段代码中,我摆脱了指针,一切都应该正常工作。 Item是10个字符,而MyStruct也是10个字符。当您返回它时,将复制这10个字符,就像一个整数一样。

但是您没有这样做;您添加了指针。由于item是局部变量,因此它位于当前堆栈帧中,而不是堆中。堆栈框架在当前作用域的末尾消失。 (但是它不会真正消失。这只是未定义的行为,超出范围引用了它,因此它可以返回任何内容。)

在没有指针的情况下进行复制意味着要进行更多的复制(尽管实际上复制并不多;在64位系统上,指针为8个字节)。这也意味着对一个副本的更改不会影响其他副本。取决于您的问题,这是好是坏。只要该结构不会变得太大,以这种方式摆脱指针实际上可以是一个很好的设计。一旦添加了指针,您将需要管理内存并决定所有权,何时释放内存以及所有其他麻烦。哪种方法最好取决于您的问题。

请注意,如果您采用这种方式,则没有理由创建中间item(这样做效率不高,因为它正在复制字符串的两个副本)。相反,您只是直接复制到st

MyStruct make(char *name) {
    MyStruct st;

    strncpy(st.item.name, name, 10);
    printf("name: %s\n", st.item.name);

    return st;
}