将一个C结构转换为另一个结构

58

我有两个完全相同但名称不同的C结构体:

typedef struct {
      double x;
      double y;
      double z;
} CMAcceleration;


typedef struct {
    double x;
    double y;
    double z;   
} Vector3d;

现在我想将一个CMAcceleration变量赋值给一个Vector3d变量(复制整个结构体)。我该如何做?

我尝试了以下代码但是得到了编译器错误:

vector = acceleration;           // "incompatible type"
vector = (Vector3d)acceleration; // "conversion to non-scalar type requested"
当然,我可以通过逐个设置成员来实现:
vector.x = acceleration.x;
vector.y = acceleration.y;
vector.z = acceleration.z;

但那似乎相当不方便。

有什么最佳解决方案吗?


1
你不可以直接使用typedef(比如:typedef struct CMAcceleration Vector3d)吗?哎呀,好像有人已经指出了…… - Nyan
9个回答

66

那是你唯一的解决方案(除了将其包装成一个函数):

vector.x = acceleration.x;
vector.y = acceleration.y;
vector.z = acceleration.z;

你实际上可以像这样进行转换(使用指针)

Vector3d *vector = (Vector3d*) &acceleration;

但是这不在规格说明中,因此行为取决于编译器、运行时和大绿色空间怪物。


4
+1:好答案。描述了既保证有效的唯一方法,也介绍了通常在实践中可行的方法,以及为什么这种方法在技术上是未定义的原因。 - Oliver Charlesworth
3
我只想补充一点,强制类型转换技术非常普遍 - 它并不像真正的邪恶。 - Prof. Falken
3
将其封装为一个函数是明智之举,即使像这样微不足道的事情也值得创建一个子程序来完成。 - alesplin
如果我们将CMAcceleration声明为struct { Vector3d vec; };会发生什么?那么CMAcceleration实例将在前sizeof(Vector3d)字节中具有Vector3d。这样做是否可以消除指针转换时的严格别名问题? - holgac
那么我们就不再需要强制转换指针了。我们可以直接赋值 vector = acc.vec; - Hermann Döppes

16

你可以使用指针来进行类型转换;

vector = *((Vector3d *) &acceleration);

6
应该指出的是,编译器没有义务确保两个结构体以相同的方式打包和对齐。 - Oliver Charlesworth
15
由于严格类型别名规则,这被视为未定义行为。详情请见:http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html - Secure
1
@Secure 很遗憾,因为我想使用这种技术来不复制而实际上是别名(更改类型)结构。 - Michael
1
@Michael:在使用gcc或clang编译代码时,必须使用“-fno-strict-aliasing”标志进行编译(其他编译器可能使用相同名称的标志,或者可能比gcc或clang的别名优化更少)。 - supercat

7

memcpy(&vector, &acceleration, sizeof(Vector3d));

请注意,这只有在内存中结构体的物理布局相同时才有效。但是,正如@Oli指出的那样,编译器不一定保证这一点!


4
应该指出的是,编译器没有义务确保两个结构体以相同的方式打包和对齐。 - Oliver Charlesworth
@Oli Charlesworth:你说得对,我已经相应地更新了答案。 - groovingandi
@OliverCharlesworth:要打破这个假设需要一款病理扭曲的编译器,特别是考虑到这个问题的被接受的答案:https://dev59.com/RWIj5IYBdhLWcg3w4Ivt - chqrlie
1
@chqrlie,你链接的被接受的答案只是说编译器必须确保布局相等,如果两个结构体作为同一联合的一部分使用。但它没有提到在联合上下文之外使用结构体的情况。因此,编译器可能会在联合情况下强制执行相等的布局,因为根据标准,它必须这样做,但在其他情况下选择不同的布局,因为某些奇怪的平台特定优化,例如。 - Mecki

6

另一种使用C99的效用函数版本:

static inline struct Vector3d AccelerationToVector(struct CMAcceleration In)
{
    return (struct Vector3d){In.x, In.y, In.z};
}

当编译器优化被打开时(例如,-Os),在调用时应该完全没有目标代码。


值得一提的是,inline只是一个提示,编译器可能会完全忽略它。 - AggelosT

6
你可以使用一个实用函数来完成这个操作:
void AccelerationToVector( struct CMAcceleration* from, struct Vector3d* to )
{
     to->x = from->x;
     to->y = from->y;
     to->z = from->z;
}

5

为什么不使用呢?

typedef CMAcceleration Vector3d;

在这种情况下,vector = acceleration; 编译得非常好,而不需要创建一个全新的结构。


我收到一个警告:'typedef struct Vector3d Vector3d'未引用未限定类型,因此不用于链接。此外,在这种情况下,CMAcceleration位于弱链接框架中,因此我避免在我的.h文件中使用它。 - Ortwin Gentz
6
如果CMAcceleration结构体来自另一个框架,建议您最好进行逐个字段的复制,而不是使用memcpy或类型转换技巧,以使您的代码在其他框架发生任何未来更改的情况下变得更加健壮。(即使您今天知道结构体布局完全相同,也可能在随后的版本中不再保持相同。) - David Gelhar

4

这可以很容易地通过使用联合实现:

typedef struct {
      double x;
      double y;
      double z;
} CMAcceleration;

typedef struct {
    double x;
    double y;
    double z;
} Vector3d;

typedef union {
    CMAcceleration acceleration;
    Vector3d vector;
} view;

int main() {
    view v = (view) (Vector3d) {1.0, 2.0, 3.0};
    CMAcceleration accel = v.acceleration;
    printf("proof: %g %g %g\n", accel.x, accel.y, accel.z);
}

2
请注意,如果您这样做,就不再遵守标准C语言规范了。是的,GCC和Clang都支持访问非当前联合成员。但是,标准并不允许这样做。 - Qqwy
1
@Qqwy 我知道我来晚了,但我认为这实际上是符合标准的。请参见C99§6.5.2.3/5(以及该页面底部的脚注#82)。 - TSmith
@TSmith 谢谢!自从五年前发布我的最后一条信息以来,我已经了解到我混淆了C语言(在其中没问题)和C++语言(在其中是未定义行为,尽管被G++和Clang++所支持)的语义。两种语言之间差异微妙的一个地方... 有关更多信息,请参见此问题/回答。 - Qqwy

2

一种安全(尽管有些复杂)的方法是使用联合:

union { CMAcceleration a, Vector3d v } tmp = { .a = acceleration };
vector = tmp.v;

自C99开始,当访问的成员不是最后一个设置的成员时,值会被重新解释。在这种情况下,我们设置了加速度然后访问向量,因此加速度被重新解释。

例如,NSRectToCGRect函数就是这样实现的。


0

你可以使用指针创建一个联合体,这样就避免了数据的复制。

例如:

struct Ocp1SyncHeader
{
    aes70::OCP1::OcaUint8 syncVal;          
    aes70::OCP1::Ocp1Header header;
};

struct convertToOcp1SyncHeader
{
    union
    {
        Ocp1SyncHeader* data;
        const uint8_t* src;
    };
    convertToOcp1SyncHeader& operator=(const uint8_t* rvalue) 
    { 
      src = (const uint8_t*)rvalue; 
      return *this; 
    }
};

** 要这样访问:

convertToOcp1SyncHeader RecvData;
RecvData = src;  // src is your block of data that you want to access

** 这样访问成员:

RecvData.data.header

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接