在没有动态内存分配的情况下复制C中的任意类型

时间:2015-09-12 10:09:02

标签: c arrays pointers language-lawyer memcpy

问题:

我想我已经找到了一种方法,据我所知,它允许你编写完全类型无关的代码,该代码在"堆栈上生成任意类型变量的副本。 (在引号中,因为C标准实际上并不需要堆栈,所以我的意思是它在本地范围内使用自动存储类复制)。这是:

/* Save/duplicate thingToCopy */
char copyPtr[sizeof(thingToCopy)];
memcpy(copyPtr, &thingToCopy, sizeof(thingToCopy));

/* modify the thingToCopy variable to do some work; do NOT do operations directly on the data in copyPtr, that's just a "storage bin". */

/* Restore old value of thingToCopy */
memcpy(&thingToCopy, copyPtr, sizeof(thingToCopy));

从我的有限测试中它起作用并且接近我可以说它应该适用于所有符合标准的C实现,但是为了防止我错过了某些内容,我想知道:

  • 这是否完全符合C标准(我相信从C89到现代的东西都应该是好的),如果没有,是否有可能修复和如何?
  • 这种方法对使用有何限制,以保持标准兼容?
    • 例如,正如我所理解的那样,只要我从不直接使用char-array临时拷贝,就可以安全地避免对齐问题 - 就像使用memcpy保存和加载的bin一样。但我无法将这些地址传递给其他函数,这些函数期望指向我正在使用的类型的指针,而不会冒对齐问题(显然语法上我可以通过首先从void *获得char *来做到这一点。 {1}},甚至没有指定我正在使用的确切类型,但重点是我认为我会通过这样做触发未定义的行为。)
  • 是否有更干净和/或更高效的方式来实现同样的目标?

*我的armel v7测试设备上的GCC 4.6.1,使用-O3优化,使用常规分配给临时变量生成与常规代码相同的代码,但可能是我的测试用例非常简单,它能够弄明白,如果更普遍地使用这种技术,它会感到困惑。

作为奖励传递兴趣,我很好奇这是否会破坏大多数与C兼容的语言(我所知道的是C ++,Objective-C,D,也许是C#,尽管其他人提及的是也欢迎。)

理由:

这就是为什么我认为上述情况有效,以防您知道我来自哪里以解释我可能犯的任何错误:

C标准"字节" (传统意义上的"最小的可寻址存储单元",而不是现代化的" 8位"含义)是char类型 - sizeof运算符以char为单位生成数字。因此,通过在该变量上使用sizeof运算符,我们可以获得任意变量类型所需的最小存储大小(我们可以在C中使用)。

C标准保证可以将所有指针类型隐式转换为void *(但如果它们的表示形式不同,则表示更改(但顺便提一下,C标准保证void *char *具有相同的表示))。

"名称"对于给定类型的数组,以及指向同一类型的指针,就语法而言基本上可以被视为相同。

sizeof运算符是在编译时计算出来的,因此我们可以char foo[sizeof(bar)]执行memcpy而不依赖于有效的非可移植VLA。

因此,我们应该能够声明一个" chars"这是保持给定类型所需的最小尺寸。

因此,我们应该能够将要复制的变量的地址和数组的名称传递给char *(据我所知,数组名称隐含地用作void *到数组的第一个元素)。由于任何指针都可以隐式转换为sizeof(需要更改表示),因此可以正常工作。

memcpy应该对我们复制到数组的变量进行按位复制。无论类型是什么,涉及任何填充比特等,memcpy保证我们将获取构成该类型的所有比特,包括填充。

由于我们无法明确地使用/声明我们刚刚复制的变量的类型,并且由于某些架构可能具有针对各种类型的对齐要求,这些黑客可能会在某些时候违反,我们无法做到直接使用此副本 - 我们必须memcpy将其重新输入我们从中获取的变量,或者使用相同类型的变量,以便使用它。但是,一旦我们将其复制回来,我们就会对我们放在首位的内容进行精确复制。基本上,我们将变量本身释放为用作临时空间。

动机(或者,"亲爱的上帝为什么!?!"):

我喜欢在有用的时候编写与类型无关的代码,但我也喜欢在C中编码,并且将两者结合起来很大程度上归结为在类似函数的宏中编写通用代码(然后你可以重新声明类型检查通过制作调用函数式宏的包装器函数定义。可以把它想象成C中真正粗糙的模板。

正如我已经做到的那样,我遇到了需要额外的临时空间变量的情况,但是,由于缺少可移植的typeof()运算符,我无法声明任何临时变量这种"通用宏的匹配类型"代码片段。这是我发现的真正便携式解决方案的最接近的东西。

因为我们可以多次执行此操作(足够大的char数组,我们可以容纳多个副本,或者几个大小足以容纳一个的char数组),只要我们可以保持{{1}}调用和复制指针直接命名,它在功能上具有复制类型的任意数量的临时变量,同时能够保持通用代码类型不可知。

P.S。为了稍微偏转可能不可避免的判断下雨,我想说我确实认识到这是非常错综复杂的,我只会在实践中保留这个经过充分测试的库代码,它显着增加了实用性,不是我经常部署的东西。

2 个答案:

答案 0 :(得分:2)

是的,它有效。是的,它是C89标准。是的,这是令人费解的。

小改进

字节表char[]可以从内存中的任何位置开始。 根据您的thingToCopy的内容,并且取决于CPU,这可能会导致副本性能不佳。

如果速度很重要(因为如果此操作很少见,可能不会),您可能更喜欢使用intlong longsize_t单位来对齐您的表格。

主要限制

只有当您知道thingToCopy的大小时,您的命题才有效。 这是一个主要问题:这意味着您的编译器需要知道编译类型中thingToCopy是什么(因此,它不能是incomplete type)。

因此,以下句子令人不安:

  

因为我们无法显式地使用/声明我们刚刚复制的变量的类型

没办法。为了编译char copyPtr[sizeof(thingToCopy)];,编译器必须知道thingToCopy是什么,因此它必须能够访问其类型!

如果你知道,你可以这样做:

thingToCopy_t save;
save = thingToCopy;
/* do some stuff with thingToCopy */
thingToCopy =  save;

更清晰,从对齐角度来看更好。

答案 1 :(得分:1)

在包含指针的对象上使用您的代码会很糟糕(const指向const的除外)。有人可能会修改指向数据或指针本身(例如realloc)。这会使您的对象副本处于意外或甚至无效的状态。

通用编程是C ++背后的主要驱动力之一。其他人试图使用宏和强制转换在C中进行泛型编程。对于小例子来说没关系,但是不能很好地扩展。当您使用这些技术时,编译器无法为您捕获错误。