C语言 如何将一个结构的内容复制到另一个结构中

67up9zun  于 2023-08-03  发布在  其他
关注(0)|答案(4)|浏览(103)

我有一个C程序,里面有两个结构

struct one{
  int a;
  int b;
  char *s;
  int c;
};

struct two{
  int a;
  int e;
  char *s;
  int d;
};

字符串
是否可以编写一个函数,将具有相同类型和名称的变量的值从struct one复制到struct two?例如,在这种情况下,函数应该这样做

two.a = one.a;
two.s = one.s;

nszi6y05

nszi6y051#

没有办法从结构中自动获取给定名称的字段。虽然你可以在Java中用反射来做类似的事情,但在C中却不能。您只需要手动复制相关成员。

p1tboqfb

p1tboqfb2#

你可以这样写函数宏:

#define CAT(A, B) A ## B
#define ARGS_ASSIGN(N, L, R, ...) CAT(_ARGS_ASSIGN, N) (L, R, __VA_ARGS__)
#define _ARGS_ASSIGN1(L, R, M0) L.M0 = R.M0;
#define _ARGS_ASSIGN2(L, R, M0, M1) L.M0 = R.M0; L.M1 = R.M1;
/* ... define sufficiently more */

字符串
并以这种方式使用:

ARGS_ASSIGN(2, two, one, a, s)

xjreopfe

xjreopfe3#

如果这真的是一个“复制同名字段”的问题,C和(据我所知)C++没有专门的简写。在C语言中,字段名并不重要,语言也没有(自动)反射功能。重要的是 addresses,字段名实际上只是引用结构中特定类型的特定地址的方式。
所以你可能只有一个使用宏的函数:

inline void one2two(struct one const * one, struct two * two)
{
    #define copy(field) two->field = one->field;
    copy(a) copy(s) // copy(whatever)
}

字符串
即使字段类型不同,使用隐式类型转换也可以工作,例如如果one->aint类型且two->adouble类型。
好吧,一个更好的设计是在两个结构体中使用一个公共结构体:

struct zero {
    int a; char * s;
};
struct one {
    struct zero z;
    int b, c;
};
struct two {
    struct zero z;
    int e, d;
}

struct one one = { /* ... */ };
struct two two;
two.z = one.z; // sure, no problem


如果你真的希望能够直接访问as字段,而不需要一直写z.,你可以使用union类型的匿名成员(从C11开始有效):

struct one {
    union {
        struct zero z;
        int a; char * s;
    };
    int b, c;
};


您的问题的标题:“如何在C中将一个结构的内容复制到另一个结构中”,以及你的完全兼容的结构(具有相同类型和相同名称的字段具有相同的地址)的示例,实际上激发了另一个主题:结构的重新解释(或类型双关)。

复制兼容struct的全部内容

在C++中,唯一高度可靠的方法似乎是使用memcpy
C语言为重新解释提供了更多的保证,这被称为类型双关。因为你的结构有兼容的类型,你可以使用指针转换:

struct one {
  int a, b; char * s; float f;
};
struct two {
  int a, b; char const * s; float volatile f;
};

struct one one = { /* ... */ };
struct two * twoPtr = (struct two *)&one;
// or
struct two two = *(struct two *)&one;


我在gcc -O2 -Wall(使用GCC 13.1.1)的最后一行得到了一个警告,但它显然是错误的。
使用twoPtr不会触发结构体的副本。使用two(最后一行)应该意味着一个副本,但如果您随后不修改one,它可能会被优化删除。请注意,取消引用指向结构的指针以访问其字段之一也有成本。
复制可能没有memcpy那么深,因为它可能会跳过未使用的字节,即在上面的示例中,在x86_64机器上,结构体的结尾有4个未使用的字节,因为sizeof(one.s) == alignof(one.s) == 8sizeof(one.f) == 4,并且结构体的结尾应该是相同类型的另一个示例的可能开始。让我们有:

struct one ones[2];


然后由4的块和任意从00开始,十六进制地址将如下:

00   ones[0].a
04   ones[0].b
08   ones[0].s
0C
10   ones[0].f
14
18   ones[1].a
2C   ones[1].b
30   ones[1].s
34
38   ones[1].f
4C


对我来说,这样的强制转换确实比使用memcpy好得多,因为后者使用的是void *,这意味着不安全。例如,如果只使用非void *类型转换,设计良好的编译器应该在类型不兼容时发出警告。
例如,这样的强制转换可以用于实现继承。GLib显然将其用于其对象系统;我可以在/usr/include/glib-2.0/gobject/gtype.h中看到它。还有许多宏,比如GTK中的GTK_BOX()GTK_WINDOW(),它们本身从GLib调用G_TYPE_CHECK_INSTANCE_CAST()。对于GTK,必须经常使用这样的宏来调用GTK对象上的函数。

  • 这是丑陋的。* 我首先提出它,因为它看起来像人们经常有这样的解决方案,也因为它是一个(可读的)一行解决你的问题。但是C实际上提供了一个非常漂亮的结构来处理你的情况:工会
union one_or_two {
    struct one one;
    struct two two;
};

struct one one = { /* ... */ };
union one_or_two one2two = { .one = one };
struct two two = one2two.two;
// or
struct two two2 = (union one_or_two){ .one = one }.two;


理论上(即没有优化)这应该意味着2个副本(你可以使用指针来代替只复制地址)。如果您确实经常需要两个结构之间的这种互操作性,那么您可能应该始终非常紧密地构思它们,并在头文件中提供联合。请注意,如果需要,您也可以使用一行解决方案:

struct two two3 = (union { struct one one; struct two two; }){ .one = one }.two;


这一点也不反常。是的,重新解释是一个相当高级的主题,但它并不是一个罕见的实践,它的可用性赋予了C语言,如果没有它,它可能不会那么有名。C union最常见的用途是实现变体(参见例如C++17’s std::variant)的值。另一个用途是重新解释。
因为你知道你通常是shouldn’t use casts,并且因为你害怕指针(或者至少你知道它们会使工作变得更复杂),所以习惯C联合。

wd2eg0qa

wd2eg0qa4#

从理论上讲,如果你确定编译器按照结构的类型定义对结构进行了排序,你可以使用下面的代码为上面的例子使用一个简单的块复制函数来完成这一点。不过,我觉得这不是个好主意。块复制使用两个相同类型的数据结构会更安全,如上面提出的答案之一中所定义的。
使用块复制功能的示例:

void main(void)
{

    struct one{
        int a;
        int b;
        char *s;
        int c;
    };

    struct two{
        int a;
        int e;
        char *s;
        int d;
    };

    // Place code that assigns variable one here

    memcpy(&two, &one, sizeof(one));
}

字符串

相关问题