【问题标题】:Why should a non-void C empty function without return statement copy a struct?为什么没有返回语句的非 void C 空函数应该复制结构?
【发布时间】:2020-07-17 07:24:11
【问题描述】:

出于某种原因,我想将一个结构深复制到另一个结构...

根据the second answer to "Deep copying structs with char arrays in C (How to copy the arrays?"),我编造了以下示例:

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

#define THE_ARRAY_SIZE 10

struct the_struct
{
    uint64_t the_member;
};

typedef struct the_struct the_struct_type;

the_struct_type *the_function(the_struct_type *the_parameter)
{
    the_struct_type *the_returned_variable = NULL;
    *the_returned_variable = *the_parameter;
    return the_returned_variable;
}

void the_show( the_struct_type *the_parameter)
{
    printf( "the_member is %" PRIu64 "\n", the_parameter->the_member);
}

int main(int argc, char *argv[]) {
    
    the_struct_type the_source;
    the_struct_type *the_target = NULL;
    
    the_source.the_member = 7777777;
    
    the_target = the_function(&the_source);
    the_show(the_target); 
}

结果是:

Segmentation fault (core dumped)

出于绝望,我评论了函数的内部情况。就好像一只无形的手在指导我的行动。 :)

the_struct_type *the_function(the_struct_type *the_parameter)
{
    //the_struct_type *the_returned_variable = NULL;
    //*the_returned_variable = *the_parameter;
    //return the_returned_variable;
}

而且,出乎意料的是,结果是我们想要的——但不确定是否符合我的预期:

the_member is 7777777

我验证了这种行为,它定义了更复杂的结构,包含数组(但不包含指针)。这种意外行为允许我通过将以下代码添加到 main 来创建结构数组:

int main (int argc, char *argv[]) {
    
    /* */
    
    the_struct_type *the_array_of_struct_type = calloc(THE_ARRAY_SIZE, sizeof *the_array_of_struct_type);
    the_array_of_struct_type[0] = *the_target;
    the_show( &the_array_of_struct_type[0] );

    the_source.the_member = 123456;
    the_target = the_function( &the_source );
    the_show(the_target);
    
    the_array_of_struct_type[1] = *the_target;
    the_show( &the_array_of_struct_type[1] );    
    the_show( &the_array_of_struct_type[0] );
}

导致:

the_member is 7777777
the_member is 7777777
the_member is 123456
the_member is 123456
the_member is 7777777

来自the first answer to "Empty return in non-void function, is undefined behaviour?" 我了解到这是一个违反约束,但结果是这个没有返回语句的非void C 空函数将输入结构深复制到输出结构。

我将把它留在这里,以防万一它对其他人有用。但是,这里还有更多的东西吗?

我猜编译器是这里的关键:我在 Cygwin x86_64 3.0.7(0.338/5/3) 和 Windows 10 下使用 gcc 7.4.0。我用简洁的方式编译:

> gcc code.c -o executable.exe
> gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/7.4.0/lto-wrapper.exe
Target: x86_64-pc-cygwin
Configured with: /cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --disable-libssp --enable-libada --disable-symvers --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible --enable-libstdcxx-filesystem-ts
Thread model: posix
gcc version 7.4.0 (GCC)

【问题讨论】:

  • 使用一个非 void 函数的返回值,该函数已经落到最后(即没有执行 return 语句)确实是未定义的行为。在您的情况下可能发生的情况是 the_parameter 只是被解释为返回值(因此实际上没有复制任何内容)。只需将原始结构的地址与“复制”的地址进行比较,您可能会发现它们是相同的
  • @DIVVS IVLIVS the_function 与彼得回答中的函数有什么关系?你彻底改变了它。
  • @DIVVSIVLIVS 我想我能理解你的意图,但这并不是真正的“复制”。当您在the_function 的第二个语句中省略*s 时,一切都已明确定义,但您只需将一个结构的地址分配给目标结构指针。你实际上没有两个结构。您有一个指针再次引用相同的结构。这也将等于the_struct_type * the_function (the_struct_type * the_parameter) { return the_parameter; }
  • @DIVVSIVLIVS 对我来说,您在问什么有点不清楚,因为标题提出了一个问题,而问题正文包含许多不相关的内容。您的问题是如标题所暗示的,还是您真的在问应该如何正确实施深拷贝?
  • @DIVVSIVLIVS ... 以及进一步:您写的是深拷贝,但代码中没有深拷贝

标签: c gcc struct cygwin deep-copy


【解决方案1】:

结果是:分段错误(核心转储)

这可能是因为您尝试取消引用 NULL 指针 the_return_variable 并通过结构的第一个字段分配不存在的指向对象,指针 the_parameter 指向,在 the_function 内部.

the_struct_type * the_returned_variable = NULL;
*the_returned_variable = *the_parameter;

要取消引用 NULL 指针会调用未定义的行为。

出于绝望,我评论了函数的内部情况。 ...而且,出乎意料的是,结果是我们想要的

结果可能是您执行时所期望的,但行为未定义,或者正如 Lundin 所说,使用缺少的非 void 函数的返回值甚至可以被视为 违反约束明确的return 语句。永远不要相信这种行为。

我知道这是违反约束的,但结果是这个没有返回语句的非 void C 空函数将输入结构深复制到输出结构。

如上所述,这是未定义的行为/违反约束。你不能相信任何事情。它也不是“复制”结构的内容。

指针parameter 的值可能被视为返回值,只是分配给main() 中的指针the_target

当您尝试打印the_show 中假定复制字段的值时,它实际上只打印原始结构的字段。

为什么没有返回语句的非 void C 空函数要复制结构?

它不应该,事实上它没有。 Peter's function 与您的示例中的 the_function 完全不同。

void deepCopyPerson (struct person *target, struct person *src)
{
    *target = *src;
}
  1. deepCopyPerson 是一个返回 void 的函数。
  2. 这里有两个参数(用于指向目标(目标)和指向源的指针。
  3. 分配直接从源到目标。

总结:完全不同。


出于某种原因,我想将一个结构深复制到另一个结构...

请注意,如果要复制的结构包含指针成员,则 Peter 的方法中所示的分配将不是 深度复制。这将是一个浅拷贝

相关:

【讨论】:

  • @4386427 我也阅读了相关问题的答案。似乎“浅拷贝”是指,当涉及作为成员的指针并且指针只是一对一地复制时,B 中的指针指的是 A 中的指针所指的东西,这里不是这种情况。其他一切,当您只复制数据对象或首先将结构复制为浅拷贝然后调整复制结构中的指针时,似乎是“深拷贝”。不确定填充是否也需要复制才能被归类为“深拷贝”。
  • 我认为这两个术语都应该只用于包含指针的结构。这是因为这两个术语都有隐含的含义,这对于常规结构是不正确的。术语“浅拷贝”意味着拷贝是不完整的,而术语“深拷贝”——至少在我看来——意味着拷贝需要某种特殊处理。因此,我只会将术语“复制”用于常规结构,而其他术语仅用于包含指针的结构。这当然只是我对此事的个人看法,并不一定反映这些术语的常用用法
  • @FelixG 是的,我想这确实有道理。
【解决方案2】:

如果您想通过值传递structunion 并将其复制到新的struct,您无需执行任何操作。

typedef struct 
{
    char name[500];
    double val[40];
    int z[400];
}my_s_t;

my_s_t foo(my_s_t s)
{
    my_s_t s1 = s;

    s1.z[4]++;

    return s1;
}

my_s_t foo1(my_s_t *s)
{
    my_s_t s1 = *s;

    s1.z[4]++;

    return s1;
}

void bar()
{
    volatile my_s_t arr[10];

    /* ......*/

    arr[2] = foo(arr[3]);
    arr[4] = foo1(arr + 5);
}

https://godbolt.org/z/8MWqGf

【讨论】:

  • volatile 为什么? “......你不必做任何事情:”听起来有点奇怪 - 毕竟代码必须进行复制:-)此外,OP似乎想要一个指向副本的指针。
  • @4386427 用于防止优化的godbolt示例
  • 好吧,但是...你不能告诉 Godbolt 为“-O0”(和/或在复制代码时删除 volatile)
  • @4386427 - -O0 会添加太多噪音
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-03
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-09
相关资源
最近更新 更多