【发布时间】:2015-12-08 20:22:17
【问题描述】:
问题:
我想我已经找到了一种方法,据我所知,它允许您编写完全与类型无关的代码,该代码在“堆栈”上复制任意类型的变量(在引号中,因为 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 一样。但是我不能将这些地址传递给其他函数,期望指向我正在使用的类型的指针,而不会冒对齐问题的风险(显然在语法上我可以通过首先从
char *获取void *来反常地做到这一点,甚至没有指定我正在使用的确切类型,但重点是我认为这样做会触发未定义的行为)。
- 例如,据我了解,只要我从不直接使用 char-array 临时副本,就可以避免对齐问题 - 就像使用 memcpy 保存和加载的 bin 一样。但是我不能将这些地址传递给其他函数,期望指向我正在使用的类型的指针,而不会冒对齐问题的风险(显然在语法上我可以通过首先从
- 是否有更简洁和/或性能更*的方法来实现相同的目标?
*GCC 4.6.1 在我的 armel v7 测试设备上,使用 -O3 优化,使用对临时变量的正常分配生成与常规代码相同的代码,但可能是我的测试用例足够简单,它能够弄清楚,如果更普遍地使用这种技术,它会变得混乱。
作为额外的兴趣,我很好奇这是否会破坏大多数与 C 兼容的语言(我知道的语言是 C++、Objective-C、D,也许还有 C#,尽管也欢迎提及其他语言)。
理由:
这就是我认为上述方法有效的原因,以防你发现知道我来自哪里有助于解释我可能犯的任何错误:
C 标准的“字节”(传统意义上的“最小可寻址内存单元”,而不是现代化的“8 位”含义)是 char 类型 - sizeof 运算符以单位为单位生成数字char。因此,我们可以通过在该变量上使用 sizeof 运算符来获得任意变量类型所需的最小存储空间(我们可以在 C 中使用)。
C 标准保证几乎所有指针类型都可以隐式转换为 void *(但如果它们的表示不同,则表示会发生变化(但顺便说一下,C 标准保证 void * 和 char * 具有相同的表示))。
就语法而言,给定类型数组的“名称”和指向同一类型的指针基本上可以同等对待。
sizeof 运算符是在编译时计算出来的,因此我们可以在不依赖有效的不可移植 VLA 的情况下执行 char foo[sizeof(bar)]。
因此,我们应该能够声明一个“字符”数组,该数组是容纳给定类型所需的最小大小。
因此,我们应该能够将要复制的变量的地址和数组的名称传递给memcpy(据我了解,数组名称隐式用作char * 到第一个元素的数组)。由于任何指针都可以隐式转换为void *(需要更改表示形式),所以这是可行的。
memcpy 应该按位复制我们要复制到数组的变量。无论类型是什么,涉及的任何填充位等,sizeof 保证我们将获取构成该类型的所有位,包括填充。
由于我们不能显式使用/声明我们刚刚复制的变量的类型,并且由于某些架构可能对各种类型有对齐要求,这种 hack 有时会违反,我们不能直接使用这个副本- 我们必须 memcpy 将它返回到我们从中获取它的变量或相同类型的变量中,以便使用它。但是一旦我们把它复制回来,我们就有了我们最初放在那里的内容的精确副本。本质上,我们正在释放变量本身以用作暂存空间。
动机(或,“天哪!?!”):
我喜欢在有用的时候编写与类型无关的代码,但我也喜欢用 C 编写代码,将两者结合起来主要是在类似函数的宏中编写通用代码(然后你可以重新声明类型检查通过制作调用类函数宏的包装函数定义)。把它想象成 C 语言中非常粗糙的模板。
当我这样做时,我遇到了需要额外的临时空间变量的情况,但是,由于缺少可移植的 typeof() 运算符,我无法在其中声明任何匹配类型的临时变量这样的“通用宏”sn-ps 代码。这是我发现的最接近真正便携的解决方案。
由于我们可以多次执行此技巧(足够大的 char 数组,我们可以容纳多个副本,或者几个 char 数组大到足以容纳一个),只要我们可以保持 memcpy 调用并直接复制指针名称,它在功能上就像拥有任意数量的复制类型的临时变量,同时能够保持通用代码类型不可知。
附:为了稍微转移可能不可避免的判断之雨,我想说我确实认识到这非常令人费解,我只会在实践中将此保留用于经过良好测试的库代码,它显着增加了有用性,而不是什么我会定期部署。
【问题讨论】:
-
这可能行不通,因为数组会衰减为指针。
-
@BasileStarynkevitch:在这种情况下适用于什么地方?
-
如果您需要编写如此糟糕的通用代码,认真考虑在其中添加一点 C++。通常我完全赞成使用纯 C 或现代 C++,但是当这导致一些令人费解的事情时......
标签: c arrays pointers language-lawyer memcpy