如何在C中将struct转换为char数组
我正在尝试将结构转换为char数组以通过网络发送。 但是,当我这样做时,我从char数组得到了一些奇怪的输出。
#include struct x { int x; } __attribute__((packed)); int main() { struct xa; ax=127; char *b = (char *)&a; int i; for (i=0; i<4; i++) printf("%02x ", b[i]); printf("\n"); for (i=0; i<4; i++) printf("%d ", b[i]); printf("\n"); return 0; }
这是各种ax值的输出(在使用gcc的X86上):
127:
7f 00 00 00
127 0 0 0
128:
ffffff80 00 00 00
-128 0 0 0
255:
ffffffff 00 00 00
-1 0 0 0
256:
00 01 00 00
0 1 0 0
我理解127和256的值,但为什么数字在转到128时会改变? 为什么不会这样:80 00 00 00 128 0 0 0
我忘记在转换过程中做某事或者我忘记了整数表示的某些事情?
*注意:这只是一个小测试程序。 在一个真正的程序中,我在结构中有更多,更好的变量名,我转换为little-endian。
*编辑:格式化
x
格式说明符本身表示参数是一个int
,并且由于该数字是负数,因此printf
需要八个字符来显示int
-sized值的所有四个非零字节。 0
修饰符告诉用零填充输出, 2
修饰符表示最小输出应该是两个字符长。 据我所知, printf
没有提供指定最大宽度的方法,除了字符串。
现在,你只传递一个char
,所以裸x
告诉函数使用传递的完整int
– 由于“ ...
”参数的默认参数提升。 尝试使用hh
修饰符告诉函数将参数视为char
而不是:
printf("%02hhx", b[i]);
你看到的是保持从char转换为int的符号。 该行为是由于在您的系统上char已签名( 注意: char未在所有系统上签名)。 如果位模式产生char的负值,那将导致负值。 将这样的char提升为int将保留符号,而int也将是负数。 请注意,即使您没有显式地放置(int)
,编译器也会在传递给printf时自动将字符提升为int。 解决方案是首先将您的值转换为unsigned char
:
for (i=0; i<4; i++) printf("%02x ", (unsigned char)b[i]);
或者,您可以从一开始就使用unsigned char*
:
unsigned char *b = (unsigned char *)&a;
然后在使用printf打印时不需要任何演员表。
char是签名类型; 所以使用二进制补码,对于一个8位整数(即一个字节),0x80为-128
将结构视为char数组是未定义的行为。 要通过网络发送,请使用正确的序列化。 这是C ++的痛苦,在C中更是如此,但它是你的应用程序独立于机器读写的唯一方式。
以您的方式将结构转换为字符或字节,当您尝试使其网络中立时,将导致问题。 为什么不现在解决这个问题呢? 您可以使用各种不同的技术,所有这些技术都可能比您尝试的更“便携”。 例如:
- 在POSIX / Unix世界中,通过函数
htonl
,htons
,ntohl
和ntohs
长期以来以机器中立的方式在网络上发送数字数据。 例如,请参阅FreeBSD或Linux系统上的byteorder(3)手册页。 - 将数据转换为完全中性的表示forms(如JSON )也是完全可以接受的。 与网络传输延迟相比,您的程序在JSON和本机表单之间转换数据所花费的时间可能会很少。
char是一个签名类型,所以你看到的是两个赞美表示,转换为(unsigned char *)将解决这个问题(Rowland只是打败了我)。
另外,您可能想要更改
for (i=0; i<4; i++) { //... }
至
for (i=0; i
char数组的签名不是问题的根源! (这是问题,但不是唯一的问题。)
对准! 这是关键词。 这就是为什么你永远不应该试图像原始记忆一样对待结构。 编译器(和各种优化标志),操作系统和月亮阶段都对结构中“相邻”字段的存储器中的实际位置做了奇怪和令人兴奋的事情。 例如,如果你有一个带有char后跟一个int的结构,那么整个结构将是内存中的EIGHT字节 – char,3个空白,无用的字节,然后是int的4个字节。 机器喜欢做这样的事情,所以结构可以很好地适应内存页面等等。
在当地大学学习机械建筑的入门课程。 同时,正确序列化。 永远不要像char数组那样处理结构。
当你去发送它时,只需使用:
(字符*)&CustomPacket
转换。 适合我。
您可能希望转换为unsigned char数组。
除非你有非常令人信服的测量表明每个八位字节都是珍贵的, 否则不要这样做 。 使用可读的ASCII协议,如SMTP , NNTP或IETF编写的许多其他精细Internet协议之一。
如果你真的必须有二进制格式,那么仅仅推断结构中的字节仍然是不安全的,因为字节顺序,基本大小或对齐约束可能因主机而异。 您必须将wire protcol设计为使用定义良好的大小并使用定义良好的字节顺序。 对于您的实现,要么使用像ntohl(3)
这样的宏,要么使用移位和屏蔽将字节放入流中。 无论您做什么,请确保您的代码在big-endian和little-endian主机上产生相同的结果。