假设我有这个交换两个变量的通用函数:
void swap(void *v1, void *v2, int size){
char buffer[size];
memcpy(buffer, v1, size);
memcpy(v1, v2, size);
memcpy(v2, buffer, size);
}
它工作正常,但我想知道在什么情况下它可能会崩溃。我想到的一种情况是当我们有两种不同的数据类型并且指定的大小不足以捕获更大的数据时。例如:
int x = 4444;
short y = 5;
swap(&x, &y, sizeof(short));
我预计当我运行它时它会给出不正确的结果,因为 memcpy
只能使用 2 个字节(而不是 4 个字节)并且部分数据会丢失或更改处理x
。
但令人惊讶的是,当我运行它时,它在我的 Windows 7 和 Ubuntu 操作系统上都给出了正确的答案。我知道 Ubuntu 和 Windows 在字节序上有所不同,但显然这不会影响这两个系统中的任何一个。
我想知道为什么通用函数在这种情况下可以正常工作。
最佳答案
要完全理解这一点,您必须了解 C 标准以及您的机器和编译器的细节。从 C 标准开始,这里有一些相关的片段[我使用的标准是 WG14/N1256],稍微总结一下:
- 有符号整数的对象表示由值位组成, 填充位和符号位。 [第 6.2.6.2.2 节]。
- 这些位存储在连续的字节序列中。 [部分 6.2.6.1].
- 如果有 N 个值位,它们代表从 2^0 到 2^{N-1}。 [第 6.2.6.2 节]。
- 符号位可以有三种含义之一,其中之一是 具有值 -2^N(二进制补码)[第 6.2.6.2.2 节]。
当您将字节从 short
复制到 int
时,您正在复制 short
的值位、填充位和符号位code> 到 int
的位,但不一定保留位的含义。有点令人惊讶的是,该标准允许这样做,除非它不保证如果您的目标实现具有所谓的“陷阱表示”并且您不幸生成了一个,则您获得的 int
将有效。
在实践中,您已经在您的机器和编译器上发现:
short
由 2 个字节表示,每个字节 8 位。- 符号位是第二个字节的第7位
- 按值升序排列的值位是字节 0 的位 0-7 和字节 1 的位 0-6。
- 没有填充位
int
由 4 个字节表示,每个字节 8 位。- 符号位是第四个字节的第7位
- 值位从小到大依次为第0字节的第0-7位、第1字节的第0-7位、第2字节的第0-7位、第3字节的第0-6位。
- 没有填充位
您还会发现这两种表示都使用二进制补码。
在图片中(其中SS是符号位,数字N对应一个值为2^N的位):
short:
07-06-05-04-03-02-01-00 | SS-14-13-12-11-10-09-08
int:
07-06-05-04-03-02-01-00 | 15-14-13-12-11-10-09-08 | 23-22-21-20-19-18-17-16 | SS-30-29-28-27-26-25-24
从这里可以看出,如果将 short
的字节复制到零 int
的前两个字节,如果符号位为零(即数字为正),因为值位完全对应。作为推论,如果您以负值 short
开始,您还可以预测您将获得不同的值,因为 short
的符号位的值为 -2^ 15 但 int
中的相应位的值为 2^15。
你在你的机器上发现的表示通常被概括为“二进制补码,小端”,但 C 标准提供了比描述所暗示的更多的表示灵 active (甚至允许一个字节有超过 8 个字节)位),这就是为什么可移植代码通常避免依赖整数类型的位/字节表示。
关于c - 使用通用函数将整数与短整数交换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29222697/