在char数组中存储一个int?

26

我想将一个4字节的整数存储在一个字符数组中... 使得字符数组的前4个位置是整数的4个字节。

然后,我想从数组中取回整数...

此外,如果有人能为我提供在循环中执行此操作的代码,那就更好了... 例如将8个整数写入32字节的数组。

int har = 0x01010101;
char a[4];
int har2;

// write har into char such that:
// a[0] == 0x01, a[1] == 0x01, a[2] == 0x01, a[3] == 0x01 etc.....

// then, pull the bytes out of the array such that:
// har2 == har

谢谢大家!

编辑:假设int占用4个字节...

编辑2:请不要担心字节序...我会关注字节序的问题。我只想知道在C/C++中实现上述功能的不同方法。谢谢。

编辑3:如果你没看出来,我正在尝试在底层编写一个序列化类...所以我正在寻找不同的策略来序列化一些常见的数据类型。


8
也许你应该自己完成家庭作业,如果有任何疑问,可以在这里发布你的代码,我们会尽力帮助你。如果你不尝试自己做,就学不到什么东西。 - jpmelos
1
如果你在写C语言,你就应该知道不要用一个值来初始化一个变量。 - jkeys
5
嗯,什么?上面只是为了传达问题。 - Polaris878
你只担心 int 类型,还是需要对非 POD 类型进行相同的操作? - jalf
1
实际上,我只需要处理POD类型(我有很多地形数据要通过网络发送)。希望我不会遇到太复杂的东西。 - Polaris878
10个回答

42

除非你关心字节顺序等细节,否则memcpy就可以解决问题:

memcpy(a, &har, sizeof(har));
...
memcpy(&har2, a, sizeof(har2));
当然,并不保证sizeof(int)==4在任何特定的实现中(实际上有一些真实世界的实现是错误的)。从这里开始编写循环应该很简单。

25

不是最优的方法,但是可以保证大小端安全。


int har = 0x01010101;
char a[4];
a[0] = har & 0xff;
a[1] = (har>>8)  & 0xff;
a[2] = (har>>16) & 0xff;
a[3] = (har>>24) & 0xff;

如果har是负数,这会出问题吗?(我记得位移和负整数有点奇怪的关系...) - Michael Anderson
为什么你需要在它们的所有位运算中使用 & 0xff 呢? - pmoubed

9
#include <stdio.h>

int main(void) {
    char a[sizeof(int)];
    *((int *) a) = 0x01010101;
    printf("%d\n", *((int *) a));
    return 0;
}

请记住:

指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果结果指针未正确对齐指向的类型,则行为是未定义的。


4
指针可以转换,但这并不意味着它可以被解引用。例如,你可以将int*转换为float*(没有未定义行为),但是一旦你尝试通过该float*写入任何内容,就会遇到未定义行为。你的例子很好,因为针对POD类型,通过char*进行写入是明确允许的,并且POD类型的生命周期从分配内存开始。但是这值得澄清。 - Pavel Minaev
2
实际上,抱歉,我错了,这个例子仍然是未定义行为 - 具体来说,没有保证 a 对于 int 正确对齐。使用 new 分配数组时,保证它们将正确对齐为与数组大小相同的任何对象;但是对于自动或静态变量或成员字段,则没有此类保证。例如,请考虑局部变量声明:char c; char a[4]; - 很有可能 a 不会分配在 4 字节边界上,在某些架构上,当您尝试通过 int* 写入该位置时,这将导致崩溃。 - Pavel Minaev
Pavel,你能解释一下POD和U.B.是什么意思吗?谢谢。 - Polaris878
2
POD = 简单旧数据。U.B. = 未定义行为。这两个术语的含义在 ISO C++ 规范中被精确定义。U.B. 基本上意味着“任何事情都可能发生,没有限制”。POD 大致意思是“C++原始类型之一,如int或float,任何指针类型,任何枚举类型,任何POD类型的数组,或者仅由POD类型字段组成的任何struct/classe/union,没有非公共成员,没有基类,没有显式构造函数或析构函数,也没有虚拟成员。” - Pavel Minaev
@PavelMinaev 指针可以转换... 标准中的下一行与您的陈述相矛盾: 如果所得指针未正确对齐指向的类型,则行为是未定义的。 让我使用您关于int和float的示例。 如果它们没有相同的对齐方式,则根据标准,转换会导致UB。 这是因为第一条规则允许转换,而下一条规则限制了转换。 - 2501
显示剩余6条评论

9
注意:通过未被最后一个分配的元素访问联合体是未定义的行为。 (假设字符是8位,整数是4字节的平台) 0xFF的位掩码将掩盖一个字符。
char arr[4];
int a = 5;

arr[3] = a & 0xff;
arr[2] = (a & 0xff00) >>8;
arr[1] = (a & 0xff0000) >>16;
arr[0] = (a & 0xff000000)>>24;

这将使arr [0]保存最高有效字节,而arr [3]保存最低有效字节。

编辑:为了让您了解诀窍,“&”是按位“与”,而“&&”是逻辑“与”。感谢评论提到的遗忘的移位。


+1,如果需要特定的二进制表示(即没有LSB / MSB混淆),那就是正确的方法。 - Pavel Minaev
1
正如Polaris878所指出的那样,由于您没有在值上使用“>>”,因此最后3个赋值将在数组中设置为“0”。 - Richard Corden

8
int main() {
    typedef union foo {
        int x;
        char a[4];
    } foo;

    foo p;
    p.x = 0x01010101;
    printf("%x ", p.a[0]);
    printf("%x ", p.a[1]);
    printf("%x ", p.a[2]);
    printf("%x ", p.a[3]);

    return 0;
}

请记住,在小端机器上,a[0]保存最低有效位(LSB),a[3]保存最高有效位(MSB)。

你对于LSB和MSB的评论只适用于小端架构。 - 1800 INFORMATION
5
在这段代码中,p.a 的读取引用了未定义的行为(U.B.),因为之前没有对 a 进行写操作。任何符合标准的 C++ 实现都有权利将对 p.x 的赋值完全优化掉,实际上有一些实现确实会这样做。 - Pavel Minaev
嗯,是的和不是的。确切的结果我猜应该是未定义的行为,因为它取决于平台架构,但联合体是一种合法的类型别名方式,如果编译器没有完全理解 p.a 已经被写入,我会感到很惊讶。事实上,在 GNU 实现中,联合体是绕过类型别名优化的唯一官方方式。 - DigitalRoss
没错,我猜是这样,但联合体并不是解决这个问题的唯一途径,而且有些解决方案并不会引发未定义行为,所以最好偏向于那些方案。 - 1800 INFORMATION
不合法将任意两种任意类型(联合或非联合)进行别名处理,但是可以通过char*来别名处理任何POD类型,并且g++也支持这样做。唯一的注意点是,为了严格符合规范,必须使用static_cast转换为char*而不是reinterpret_cast或C风格的转换(这意味着您必须首先将其转换为void*),尽管我没有看到任何实现在最后一位上实际上有任何区别... - Pavel Minaev
显示剩余2条评论

8
请勿使用联合体(union),Pavel澄清道:
这是不可取的,因为C++禁止访问任何除最后一个被写入的联合成员之外的成员。特别地,在上述代码中,编译器有权完全优化掉对int成员的赋值,因为其值不会在随后被使用(它仅看到了对char[4]成员的后续读取,在那里没有义务提供任何有意义的值)。在实践中,特别是g ++以众所周知的技巧而著称,因此这并不只是理论上的问题。另一方面,使用static_cast>后跟static_cast>则保证可行。
- Pavel Minaev

1
这是U.B.,因为C++禁止访问除最后一个被写入的联合成员之外的任何成员。特别地,编译器可以自由地通过上述代码完全优化掉对int成员的赋值,因为它的值不会随后被使用(它只看到对char[4]成员的后续读取,并没有义务在那里提供任何有意义的值)。实际上,g++尤其擅长这样的技巧,所以这不仅仅是理论。另一方面,使用static_cast<void*>后跟static_cast<char*>是保证可行的。 - Pavel Minaev
我就是这么想的,虽然我从未明确说明。如果您不介意的话,我会把您的评论当作建议留下来。 - GManNickG
我不介意,但修复那些 static_cast 会更好 :) - Pavel Minaev

4
您还可以使用放置new来实现这一点:
void foo (int i) {
  char * c = new (&i) char[sizeof(i)];
}

2
#include <stdint.h>
int main(int argc, char* argv[]) { /* 在循环中使用8个整数 */ int i; int* intPtr; int intArr[8] = {1, 2, 3, 4, 5, 6, 7, 8}; char* charArr = malloc(32);
for (i = 0; i < 8; i++) { intPtr = (int*) &(charArr[i * 4]); /* ^ ^ ^ ^ */ /* 指向 | | | */ /* 强制类型转换为 int* | | */ /* 地址 | */ /* 字符数组中的位置 */
*intPtr = intArr[i]; /* 在指针指向的位置写入整数 */ }
/* 读取整数 */ for (i = 0; i < 8; i++) { intPtr = (int*) &(charArr[i * 4]); intArr[i] = *intPtr; }
char* myArr = malloc(13); int myInt; uint8_t* p8; /* 无符号8位整数 */ uint16_t* p16; /* 无符号16位整数 */ uint32_t* p32; /* 无符号32位整数 */
/* 使用除了4字节整数之外的大小,将myArr中所有位设置为1 */ p8 = (uint8_t*) &(myArr[0]); p16 = (uint16_t*) &(myArr[1]); p32 = (uint32_t*) &(myArr[5]); *p8 = 255; *p16 = 65535; *p32 = 4294967295;
/* 获取值 */ p16 = (uint16_t*) &(myArr[1]); uint16_t my16 = *p16;
/* 将16位整数放入常规整数中 */ myInt = (int) my16;
}

1
char a[10];
int i=9;

a=boost::lexical_cast<char>(i)

我发现这是将字符转换为整数和反之的最佳方法。
sprintf是boost::lexical_cast的替代方案。
char temp[5];
temp[0]="h"
temp[1]="e"
temp[2]="l"
temp[3]="l"
temp[5]='\0'
sprintf(temp+4,%d",9)
cout<<temp;

输出将是:hell9

0
联合体 value { int i; char bytes[sizeof(int)]; };
value v; v.i = 2;
char* bytes = v.bytes;

1
给你的答案添加一些解释可以帮助读者理解。 - Suraj Bajaj

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