[精彩] char[0], sizeof和struct padding

来源:互联网 发布:师洋淘宝 编辑:程序博客网 时间:2024/04/30 22:43

 我想举一个自己最近在项目中犯的错误来说明要踏踏实实做人,不要做装B青年 :(
在代码中,我需要在一个library和一个daemon之间通过socket传送数据包,包的格式定义如下(为了简化,我就用最简单的数据类型举例):


typedef struct {
        int head;
        int size; //指明整个包的长度
        char reply;
        char data[0];
} packet;

packet*  cmd = malloc (sizeof(packet) + 20);
memcpy (packet->data, some_data, 20);



daemon将上面分配的cmd包发送给library,library接收到包后,需要将data字段中的数据取出来。size指明了整个包的长度,但没有字段指明数据的长度。我需要这么一个指明数据长度的字段吗?作为一个装B青年,我认为当然不需要,于是我这样来计算数据的长度:


#define offsetof(type, element) ((int)&((type *)0)->element)
static inline size_t packet_data_len(packet* cmd) {
    assert(cmd);
    return cmd->size - offsetof(packet, data);
}

memcpy (buffer_to_receive_data, cmd->data, packet_data_len (cmd));



于是乎,这段程序成功的给我带来了无数的bug,莫名奇妙的segfault,奇怪的数据错误,还是有部分时间的正常工作。当然,最终我还是找到了问题:
sizeof (packet) == 12;
这是合理的,char reply被padding成了4个字节,而char data[0]字节为0。
但,offsetof(packet, data) == 9,在计算偏移时,char reply为一个字节,没有padding。
所以packet_data_len每次都会返回比真实的数据多3个字节 ……

最后我还是老老实实加了个data_len字段指明数据的长度,并告诫自己,本本分分写程序,莫装B ……


 mik 回复于:2009-05-17 00:31:25

我常讲的,代码的逻辑清晰要比任何小技巧要重要得多。
最简单方法就是最有效的方法。
拒绝淫技,当然 linux kernel 除外,偶不敢批


 edigar 回复于:2009-05-17 00:41:54

学习,谢谢楼主分享经验:mrgreen:


 zx_wing 回复于:2009-05-17 00:47:17

引用:原帖由 mik 于 2009-5-17 00:31 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10560719&ptid=1455677]
我常讲的,代码的逻辑清晰要比任何小技巧要重要得多。
最简单方法就是最有效的方法。
拒绝淫技,当然 linux kernel 除外,偶不敢批 


是的,我也从来都是这样个别人说的。
但自己却从来喜欢用些奇技淫巧,惭愧


 jovistar 回复于:2009-05-17 16:46:57

还是老老实实的比较好,起码能让逻辑看上去比较清晰明了


 yhb04 回复于:2009-05-17 18:08:16

引用:原帖由 zx_wing 于 2009-5-17 00:26 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10560715&ptid=1455677]
我想举一个自己最近在项目中犯的错误来说明要踏踏实实做人,不要做装B青年 :(
在代码中,我需要在一个library和一个daemon之间通过socket传送数据包,包的格式定义如下(为了简化,我就用最简单的数据类型举例 ... 


这算鸟奇技淫巧。
LZ既然用到GCC的零长数组,就应该仔细阅读结构布局的说明。显然LZ没仔细搞清楚GCC的C扩展就乱用。
要是嫌阅读说明麻烦,把最后的data[0]改成data[1]不什么事都没了嘛。


 baicj 回复于:2009-05-17 18:24:05

引用:原帖由 yhb04 于 2009-5-17 18:08 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562000&ptid=1455677]

这算鸟奇技淫巧。
LZ既然用到GCC的零长数组,就应该仔细阅读结构布局的说明。显然LZ没仔细搞清楚GCC的C扩展就乱用。
要是嫌阅读说明麻烦,把最后的data[0]改成data[1]不什么事都没了嘛。 


看贴不仔细, 改成data[1]不解决问题


 yhb04 回复于:2009-05-17 18:58:28

引用:原帖由 baicj 于 2009-5-17 18:24 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562026&ptid=1455677]

看贴不仔细, 改成data[1]不解决问题 


???
怎么不解决问题?说来听听。


 yhb04 回复于:2009-05-17 19:11:09

typedef struct {
        int head;
        int size; //指明整个包的长度
     char reply;
        char data[1];
} packet;

packet*  cmd = malloc (sizeof(packet) + 20); //多空几个字节无所谓
memcpy (packet->data, some_data, 20);

#define offsetof(type, element) ((unsigned long)&((type *)0)->element)
cmd->size = offsetof(packet, data) + 20;//cmd->size的计算不要用sizeof(packet)



static inline size_t packet_data_len(packet* cmd) {
    assert(cmd);
    return cmd->size - offsetof(packet, data);
}

memcpy (buffer_to_receive_data, cmd->data, packet_data_len (cmd));




或者直接用cmd->size记录实际数据的长度,数据在cmd->data[0]处开始。


 baicj 回复于:2009-05-17 19:58:02

引用:原帖由 yhb04 于 2009-5-17 19:11 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562110&ptid=1455677]
 ...
#define offsetof(type, element) ((unsigned long)&((type *)0)->element)
cmd->size = offsetof(packet, data) + 20;//cmd->size的计算不要用sizeof(packet)
 ... 


如同你所说, 问题在于daemon端用sizeof计算的struct head大小, 而library端用offsetof计算struct head大小, 两者结果不一致. 修改位data[1]不会有帮助, 解决问题的办法也正如你的代码, 统一用offsetof来代替sizeof, 或者用sizeof来代替offsetof.


 ilex 回复于:2009-05-17 20:24:41

好帖子;


一般都是不吃亏不长记性的。

就像新员工来后,很鄙视我们代码中的一些做法,认为这个那个运行效率低,

搞了几次,出了几个BUG后,就知道学乖了。

当然,楼主肯定是个老鸟,不是新员工。


 xman0017 回复于:2009-05-17 21:03:47

char data[0];

改为: char*data

应该没有问题


 zx_wing 回复于:2009-05-17 22:29:02

引用:原帖由 yhb04 于 2009-5-17 19:11 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562110&ptid=1455677]
typedef struct {
        int head;
        int size; //指明整个包的长度
     char reply;
        char data[1];
} packet;

packet*  cmd = malloc (sizeof(packet) + 20); //多空几个字节无所谓
... 


呵呵,小伙子水平不高口气不小。
首先char data[1]解决不了问题,char data[0]不是C标准,前者是C标准而已。
其次,gcc手册没有对零长数组有太多解释,这本来就是个简单的东西。其次这里的内存布局和是不是零长数组没有关系,把char data[0]换成char data是一样。不过你还是说对了,这是内存布局的问题引起的,具体的说着取决于padding的位置。我怀疑你是猜的,因为编译手册是不会讲这个内存布局的,不同的架构对padding有不同的规定。这个是架构的ABI或software conventions规定的。例如


struct example {
        int a;
        int b;
        char c;
        char d;
};

sizeof (struct example) == 12; 
offsetof (struct example, d) == 9;


恍然一看似乎offsetof在计算偏移的时候没有算padding,实际不然,padding在最后两个字节,所以上面例子的内存布局是:
a(4 bytes) b(4 bytes) c(1 byte) d(1 byte) padding (2 bytes)
对于我用的 x86平台,这个其在ABI手册有规定:
引用:
Each member is assigned to the lowest available offset with the appropriate
alignment. [color=red]This may require internal padding[/color], [color=red]depending on the previous
member.
[/color]


这里d前面的c同样是char类型,所以两者之间没有padding,而根据另一条规定:
引用:
A structure¡¯s size is increased, if necessary, to make it a multiple of the
alignment. [color=red]This may require tail padding, depending on the last member.[/color]


把padding放在了最后,即tail padding。

如果有一种架构使用不同的padding方式,例如:
a(4 bytes) b(4 bytes) c(1 byte) padding (1 byte) d(1 byte) padding (1 byte)
则offsetof (struct example, d) == 10;
gcc关于零长数组的章节有点长,并且和这里的内容没有什么关系,就不贴了。我估计你也没看过,它的标题是“5.14 Arrays of Length Zero”,不信你可以去看看。

[ 本帖最后由 zx_wing 于 2009-5-17 23:15 编辑 ]


 mik 回复于:2009-05-17 22:50:55

zx_wing 就别和他较劲了。正如你说他的:水平不高口气不小。
偶领教过,说话牛哄哄的,特拽:mrgreen:


 tyc611 回复于:2009-05-17 23:17:06

如果是我,我会分为消息头和数据两部分


 zx_wing 回复于:2009-05-17 23:29:26

引用:原帖由 baicj 于 2009-5-17 19:58 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562269&ptid=1455677]

如同你所说, 问题在于daemon端用sizeof计算的struct head大小, 而library端用offsetof计算struct head大小, 两者结果不一致. 修改位data[1]不会有帮助, 解决问题的办法也正如你的代码, 统一用offsetof来代替s ... 


是的,关键问题在于offsetof和sizeof计算得来head大小不一样。
我刚才google了一下发现问这个问题的人还很多,但没人回答,下面这个帖子里的老外就以为对齐出错了:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=253387

实际上问题仅仅是我们错误的假设的padding的位置而已。


 zx_wing 回复于:2009-05-17 23:31:49

引用:原帖由 mik 于 2009-5-17 22:50 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562822&ptid=1455677]
zx_wing 就别和他较劲了。正如你说他的:水平不高口气不小。
偶领教过,说话牛哄哄的,特拽:mrgreen: 


呵呵,也不是较劲。只是借着话题顺便把问题讲清楚了,供遇到同样问题或以后遇到同样问题的朋友借鉴。


 yhb04 回复于:2009-05-17 23:33:45

引用:原帖由 zx_wing 于 2009-5-17 22:29 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562734&ptid=1455677]

呵呵,小伙子水平不高口气不小。
首先char data[1]解决不了问题,char data[0]不是C标准,前者是C标准而已。
其次,gcc手册没有对零长数组有太多解释,这本来就是个简单的东西。其次这里的内存布局和是不是 ... 


猜不猜不是你说了算,现在写一个C编译器也不是天方夜谈,你对结构体的布局不了解并不表示别人也这样。装B不是靠发帖数多。您爱装就继续装吧。
对了,GCC的零长数组满足不了您的需求,倒是C99 里的东西可以符合你的要求。不过现在不知道有那个编译器是些了它没有。


 yhb04 回复于:2009-05-17 23:38:39

引用:原帖由 mik 于 2009-5-17 22:50 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562822&ptid=1455677]
zx_wing 就别和他较劲了。正如你说他的:水平不高口气不小。
偶领教过,说话牛哄哄的,特拽:mrgreen: 


还有你,倒是真的水平不高,口气不小。
上次花了1个小时还找了个错误的源文件:luya: 
论抬杠您水平确实不赖。有个版主头衔说话就这么吊。


 sep 回复于:2009-05-17 23:42:41

学习,lz的贴都是好贴


 zx_wing 回复于:2009-05-17 23:42:52

引用:原帖由 yhb04 于 2009-5-17 23:33 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562933&ptid=1455677]

猜不猜不是你说了算,现在写一个C编译器也不是天方夜谈,你对结构体的布局不了解并不表示别人也这样。装B不是靠发帖数多。您爱装就继续装吧。
对了,GCC的零长数组满足不了您的需求,倒是C99 里的东西可以符 ... 


哈哈,小伙子,装B也不是靠说话拽就可以了。要像我这样,拿出大量的证据证明装的正确。
还有一点大忌就是,装之前要还是要真懂一点再装,不然像你这样稍微装一下就露马脚就丢人了。

>>你对结构体的布局不了解并不表示别人也这样
这个我确实不了解,被你说中了,我也是这次遇到问题才去查的。

这样,我给你个装的机会,我就列4种架构(我不敢列多了,我手里头只有这几种的资料),你来给我讲讲它们的结构体布局的规定(组合要全哦)。不能瞎说哦,要有证据,你可以引用你所说的编译器手册里的相关章节,我还真不知道编译器手册在什么地方讲的。:mrgreen: 
1. x86
2. x86-64
3. IA64
4. MIPS

[ 本帖最后由 zx_wing 于 2009-5-17 23:48 编辑 ]


 yhb04 回复于:2009-05-17 23:50:35

引用:原帖由 zx_wing 于 2009-5-17 23:42 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562954&ptid=1455677]

哈哈,小伙子,装B也不是靠说话拽就可以了。要像我这样,拿出大量的证据证明装的正确。
还有一点大忌就是,装之前要还是要真懂一点再装,不然像你这样稍微装一下就露马脚就丢人了。 


你的证据可真多呀,我没什么好说的了,拜托你把我露的马脚都指出来,让我也见识见识高手的水平啊。


 yhb04 回复于:2009-05-17 23:55:13

引用:原帖由 zx_wing 于 2009-5-17 23:42 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562954&ptid=1455677]

哈哈,小伙子,装B也不是靠说话拽就可以了。要像我这样,拿出大量的证据证明装的正确。
还有一点大忌就是,装之前要还是要真懂一点再装,不然像你这样稍微装一下就露马脚就丢人了。

>>你对结构体的布局不 ... 


要讲内存布局也不用扯这么多平台。
从你的第一贴就可以看出你连自己的开发平台的对齐就不清楚,扯这么多平台显得你很厉害是吧、可喜第一贴就把你自己的马脚给露了。


 zx_wing 回复于:2009-05-17 23:56:57

引用:原帖由 yhb04 于 2009-5-17 23:50 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562958&ptid=1455677]

你的证据可真多呀,我没什么好说的了,拜托你把我露的马脚都指出来,让我也见识见识高手的水平啊。 


哈哈,不跟你抬杠了。
你有没有马脚看了你的帖子和看了后面的回复的朋友自己会判断。


 zx_wing 回复于:2009-05-18 00:02:50

引用:原帖由 yhb04 于 2009-5-17 23:55 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562967&ptid=1455677]

要讲内存布局也不用扯这么多平台。
从你的第一贴就可以看出你连自己的开发平台的对齐就不清楚,扯这么多平台显得你很厉害是吧、可喜第一贴就把你自己的马脚给露了。 


你小子还真来劲了呢。本来都不想和你扯,你还起劲。
你起劲就算了,还净乱讲,新到CU的朋友看你这么牛B轰轰的嚷嚷我不说话,还以为你讲的是对的哦。最烦你这种不懂又要在这里误导人的。

>>要讲内存布局也不用扯这么多平台。
我前面已经讲了,内存布局取决于架构,在架构的ABI和software coventions中规定,你以为所有平台都一样哦。我都贴出具体的内容了你还乱讲啥。这年头,装B要讲证据,不要信口雌黄


 yhb04 回复于:2009-05-18 00:03:25

引用:原帖由 zx_wing 于 2009-5-17 23:56 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562972&ptid=1455677]

哈哈,不跟你抬杠了。
你有没有马脚看了你的帖子和看了后面的回复的朋友自己会判断。 


刚还说你说什么都有N出处的,现在就说我在抬杠了。你倒是指出我的那个回复露马脚。


 sep 回复于:2009-05-18 00:05:41

引用:原帖由 yhb04 于 2009-5-18 00:03 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562985&ptid=1455677]

刚还说你说什么都有N出处的,现在就说我在抬杠了。你倒是指出我的那个回复露马脚。 


拜托你首先用vs好,gcc好,先把自己的代码测试一下吧


 yhb04 回复于:2009-05-18 00:06:21

引用:原帖由 zx_wing 于 2009-5-18 00:02 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562983&ptid=1455677]

你小子还真来劲了呢。本来都不想和你扯,你还起劲。
你起劲就算了,还净乱讲,新到CU的朋友看你这么牛B轰轰的嚷嚷我不说话,还以为你讲的是对的哦。最烦你这种不懂又要在这里误导人的。

>>要讲内存布局也 ... 


那你把具体平台都贴出来好了,你贴出来吗、


 zx_wing 回复于:2009-05-18 00:07:24

引用:原帖由 yhb04 于 2009-5-18 00:03 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562985&ptid=1455677]

刚还说你说什么都有N出处的,现在就说我在抬杠了。你倒是指出我的那个回复露马脚。 


>>LZ既然用到GCC的零长数组,就应该仔细阅读结构布局的说明。显然LZ没仔细搞清楚GCC的C扩展就乱用。
把你说的说明给我找出来,贴到这里。我倒是孤陋寡闻不清楚编译手册什么时候会规定架构ABI管的东西

>>要是嫌阅读说明麻烦,把最后的data[0]改成data[1]不什么事都没了嘛。
给我讲讲data[1]有什么用。把内存布局画出来,证明这样改有用。


 mik 回复于:2009-05-18 00:08:44

跟他较劲,真没必要。

我都放弃了。

你就歇歇睡吧。


 zx_wing 回复于:2009-05-18 00:10:25

引用:原帖由 mik 于 2009-5-18 00:08 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562999&ptid=1455677]
跟他较劲,真没必要。

我都放弃了。

你就歇歇睡吧。 


还是听你的。
我真是傻B了,还是不够淡定啊:mrgreen: 
就此打住,坚决不继续和他纠缠


 yhb04 回复于:2009-05-18 00:17:37

引用:原帖由 zx_wing 于 2009-5-18 00:07 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562997&ptid=1455677]

>>LZ既然用到GCC的零长数组,就应该仔细阅读结构布局的说明。显然LZ没仔细搞清楚GCC的C扩展就乱用。
把你说的说明给我找出来,贴到这里。我倒是孤陋寡闻不清楚编译手册什么时候会规定架构ABI管的东西

>>要 ... 


>>要是嫌阅读说明麻烦,把最后的data[0]改成data[1]不什么事都没了嘛。
给我讲讲data[1]有什么用。把内存布局画出来,证明这样改有用。
你要我说什么呢,按照你上面定义的结构,以X86的某个典型编译器为例,data[1]跟前面的char变量紧邻,但是整个结构的大小根据对齐原则data[0]的后面有两个字节的padding。


 wzw19191 回复于:2009-05-18 01:08:50

几位都是高手。相互争论一下也没什么不可以的,相反更有利于真相的出现。
问题弄明白目的就达到了。最重要的是我等小辈又长见识了,又能认识到自己的不足。
希望能向大佬们学习。


 gawk 回复于:2009-05-18 08:58:30

呵呵,装B被雷劈:mrgreen:


 zx_wing 回复于:2009-05-18 09:07:08

引用:原帖由 yhb04 于 2009-5-18 00:17 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563013&ptid=1455677]

>>要是嫌阅读说明麻烦,把最后的data[0]改成data[1]不什么事都没了嘛。
给我讲讲data[1]有什么用。把内存布局画出来,证明这样改有用。
你要我说什么呢,按照你上面定义的结构,以X86的某个典型编译器为例, ... 


呵呵,data[0]也和前面的紧邻的
如果仅仅是malloc(sizeof(packet)),则data[1]情况变为:
head (4 bytes) size (4 bytes) reply (1 byte) data[1] (1 bytes) padding (2 bytes)
如果是data[0]:
head (4 bytes) size (4 bytes) reply (1 byte) data[0] (0 bytes) padding (3 bytes)

不管data[0],data[1],malloc(sizeof(packet) + 20)后,之后都没有padding。所以两者内存布局本质是一样的,把data[0]改成data[1]没有任何帮助。


你跟我讲技术,我就心平气和的给你讲

[ 本帖最后由 zx_wing 于 2009-5-18 09:12 编辑 ]


 yhb04 回复于:2009-05-18 09:55:45

引用:原帖由 zx_wing 于 2009-5-18 09:07 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563428&ptid=1455677]

呵呵,data[0]也和前面的紧邻的
如果仅仅是malloc(sizeof(packet)),则data[1]情况变为:
head (4 bytes) size (4 bytes) reply (1 byte) data[1] (1 bytes) padding (2 bytes)
如果是data[0]:
head (4  ... 


谁说内存内存布局不一样了,只是改成data[1]啥的就可以正确求的offset(data,packet)的值了。
gcc的data[0]的实现就比较诡异了,实际应用中用这个东西是为了应付变长结构,这个时候使用sizeof(packet)是不适宜的,因为它考虑了结构末尾的padding。
我本来说了C99里面的data[]满足你的要求。

C99:
16 As a special case, the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member. With two
exceptions, the flexible array member is ignored. First, the size of the structure shall be
equal to the offset of the last element of an otherwise identical structure that replaces the
flexible array member with an array of unspecified length.106) Second, when a . (or ->)
operator has a left operand that is (a pointer to) a structure with a flexible array member
and the right operand names that member, it behaves as if that member were replaced
with the longest array (with the same element type) that would not make the structure
larger than the object being accessed; the offset of the array shall remain that of the
flexible array member, even if this would differ from that of the replacement array. If this
array would have no elements, it behaves as if it had one element but the behavior is
undefined if any attempt is made to access that element or to generate a pointer one past
it.
17 EXAMPLE Assuming that all array members are aligned the same, after the declarations:
struct s { int n; double d[]; };
struct ss { int n; double d[1]; };
the three expressions:
sizeof (struct s)
offsetof(struct s, d)
offsetof(struct ss, d)
have the same value. The structure struct s has a flexible array member d.

PS:如果你不和俺抬杠,俺也可以心平气和的和你谈。


 yhb04 回复于:2009-05-18 09:59:15

还有,这个问题本身跟各个平台的ABI没有太大关系。
我只假设你在X86平台上用gcc编译2个程序,互相通信。
如果你说你的一个程序是X86的gcc编译的程序,另一个是其他平台其他编译器编译的程序。那当我前面什么都没说。


 zx_wing 回复于:2009-05-18 10:03:32

引用:原帖由 yhb04 于 2009-5-18 09:55 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563890&ptid=1455677]

谁说内存内存布局不一样了,只是改成data[1]啥的就可以正确求的offset(data,packet)的值了。
gcc的data[0]的实现就比较诡异了,实际应用中用这个东西是为了应付变长结构,这个时候使用sizeof(packet)是不适宜 ... 


》》只是改成data[1]啥的就可以正确求的offset(data,packet)的值了、
data[0],data[1]通过offsetof求值是一样的,因为它们之前的内存布局相同


 zx_wing 回复于:2009-05-18 10:04:34

引用:原帖由 yhb04 于 2009-5-18 09:59 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563921&ptid=1455677]
还有,这个问题本身跟各个平台的ABI没有太大关系。
我只假设你在X86平台上用gcc编译2个程序,互相通信。
如果你说你的一个程序是X86的gcc编译的程序,另一个是其他平台其他编译器编译的程序。那当我前面什么都 ... 


当然有关系。如果ABI规定padding在reply之后data之前,结果就完全不一样


 yhb04 回复于:2009-05-18 10:05:11

引用:原帖由 zx_wing 于 2009-5-18 10:03 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563954&ptid=1455677]

》》只是改成data[1]啥的就可以正确求的offset(data,packet)的值了、
data[0],data[1]通过offsetof求值是一样的,因为它们之前的内存布局相同 


typedef struct {
        int head;
        int size; //指明整个包的长度
     char reply;
        char data[1];
} packet;

packet*  cmd = malloc (sizeof(packet) + 20); //多空几个字节无所谓
memcpy (packet->data, some_data, 20);

#define offsetof(type, element) ((unsigned long)&((type *)0)->element)
cmd->size = offsetof(packet, data) + 20;//cmd->size的计算不要用sizeof(packet)



static inline size_t packet_data_len(packet* cmd) {
    assert(cmd);
    return cmd->size - offsetof(packet, data);
}

memcpy (buffer_to_receive_data, cmd->data, packet_data_len (cmd));




或者直接用cmd->size记录实际数据的长度,数据在cmd->data[0]处开始。


 yhb04 回复于:2009-05-18 10:06:55

引用:原帖由 zx_wing 于 2009-5-18 10:04 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563964&ptid=1455677]

当然有关系。如果ABI规定padding在reply之后data之前,结果就完全不一样 


据我所知,结构体内的域之间的padding不是ABI规定的,而是编译器的具体实现。

C89:
Each non-bit-field member of a structure or union object is aligned
in an implementation-defined manner appropriate to its type.

   Within a structure object, the non-bit-field members and the units
in which bit-fields reside have addresses that increase in the order
in which they are declared.  A pointer to a structure object, suitably
cast, points to its initial member (or if that member is a bit-field,
then to the unit in which it resides), and vice versa.  There may
therefore be unnamed holes within a structure object, but not at its
beginning, as necessary to achieve the appropriate alignment.

   The size of a union is sufficient to contain the largest of its
members.  The value of at most one of the members can be stored in a
union object at any time.  A pointer to a union object, suitably cast,
points to each of its members (or if a member is a bit-field, then to
the unit in which it resides), and vice versa.

   There may also be unnamed padding at the end of a structure or
union, as necessary to achieve the appropriate alignment were the
structure or union to be a member of an array.


 zx_wing 回复于:2009-05-18 10:10:12

引用:原帖由 yhb04 于 2009-5-18 10:06 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563984&ptid=1455677]

据我所知,结构体内的域之间的padding不是ABI规定的,而是编译器的具体实现。

C89:
Each non-bit-field member of a structure or union object is aligned
in an implementation-defined manner appro ... 


嗯,现在像讲技术的态度了。
implementation-defined manner,确实是编译具体实现,但编译器的具体实现遵照的就是架构ABI。不论是编译器还是操作系统,都是必须遵照它。
implementation-defined manner 指就是对于不同的架构其实现可能不一样,因为架构的ABI不一样。


 yhb04 回复于:2009-05-18 10:13:54

引用:原帖由 zx_wing 于 2009-5-18 10:10 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10564016&ptid=1455677]

嗯,现在像讲技术的态度了。
implementation-defined manner,确实是编译具体实现,但编译器的具体实现遵照的就是架构ABI。不论是编译器还是操作系统,都是必须遵照它。
implementation-defined manner 指就 ... 


ABI主要控制的是操作系统与应用程序的系统调用的惯例,虽然在系统调用中如果传结构体参数,那么结构体必须遵照OS的ABI来控制padding,但是这并不是强制的。


 zx_wing 回复于:2009-05-18 10:14:26

引用:原帖由 yhb04 于 2009-5-18 10:05 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10563968&ptid=1455677]

typedef struct {
        int head;
        int size; //指明整个包的长度
     char reply;
        char data[1];
} packet;

packet*  cmd = malloc (sizeof(packet) + 20); //多空几个字节无所谓 ... 


你这样改之所以对,不是因为把data[0]改成data[1]了,10楼就已经指出了原因
引用:
如同你所说, 问题在于daemon端用sizeof计算的struct head大小, 而library端用offsetof计算struct head大小, 两者结果不一致. 修改位data[1]不会有帮助, 解决问题的办法也正如你的代码, 统一用offsetof来代替sizeof, 或者用sizeof来代替offsetof.


所以即使是data[0]也是一样。

此外要改的彻底,packet*  cmd = malloc (sizeof(packet) + 20);直接改成packet*  cmd = malloc (offsetof(packet, data) + 20);


 yhb04 回复于:2009-05-18 10:15:00

引用:原帖由 zx_wing 于 2009-5-18 10:10 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10564016&ptid=1455677]

嗯,现在像讲技术的态度了。
implementation-defined manner,确实是编译具体实现,但编译器的具体实现遵照的就是架构ABI。不论是编译器还是操作系统,都是必须遵照它。
implementation-defined manner 指就 ... 


还有,我前面的代码能否解决你的问题?


 zx_wing 回复于:2009-05-18 10:15:56

引用:原帖由 yhb04 于 2009-5-18 10:13 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10564054&ptid=1455677]

ABI主要控制的是操作系统与应用程序的系统调用的惯例,虽然在系统调用中如果传结构体参数,那么结构体必须遵照OS的ABI来控制padding,但是这并不是强制的。 


呵呵,该讲的我都讲了。
你也可以到编译器版发贴求证我说的是否正确。


 yhb04 回复于:2009-05-18 10:17:01

所以即使是data[0]也是一样。

此外要改的彻底,packet*  cmd = malloc (sizeof(packet) + 20);直接改成packet*  cmd = malloc (offsetof(packet, data) + 20);

没错啊,我本来就是这个意思,gcc的data[0]的结构体的sizeof很诡异,就不应该用sizeof。


 Beetle-K-V 回复于:2009-05-18 10:20:51

一群疯子,个个都把自己当个玩意。


 poxma 回复于:2009-05-18 11:01:39

如果要发个数据包
格式就像下面这个结构一样
typedef struct {
        int head;
        int size; 
     char reply;
        char data[0];
} packet;
遇到发送端和接收端的int类型大小不同怎么办啊


 yulc 回复于:2009-05-18 11:29:41

引用:原帖由 zx_wing 于 2009-5-17 00:26 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10560715&ptid=1455677]
我想举一个自己最近在项目中犯的错误来说明要踏踏实实做人,不要做装B青年 :(
在代码中,我需要在一个library和一个daemon之间通过socket传送数据包,包的格式定义如下(为了简化,我就用最简单的数据类型举例 ... 



得好复杂,你把结构体改为:
typedef struct {
        int head;
        int size; //指明整个包的长度
      char reply;
        [color=Red]char pad[3]; // 补齐[/color]
        char data[0];
} packet;

就OK了呀.


 ShadowStar 回复于:2009-05-18 12:32:15

引用:原帖由 zx_wing 于 2009-5-17 00:26 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10560715&ptid=1455677]
我想举一个自己最近在项目中犯的错误来说明要踏踏实实做人,不要做装B青年 :(
在代码中,我需要在一个library和一个daemon之间通过socket传送数据包,包的格式定义如下(为了简化,我就用最简单的数据类型举例 ... 



其实,我觉得,LZ把

typedef struct {
        int head;
        int size; //指明整个包的长度
        char reply;
        char data[0];
} packet;


改为

typedef struct {
struct {
        int head;
        int size; //指明整个包的长度
        char reply;
};
        char data[0];
} packet;


就OK了。


 kouu 回复于:2009-05-18 13:15:16

LZ的帖子不是做经验分享的么? 怎么好多人理解成求助帖了…… 搞不懂:-L


 FeCen 回复于:2009-05-18 13:28:33

引用:原帖由 tyc611 于 2009-5-17 23:17 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10562905&ptid=1455677]
如果是我,我会分为消息头和数据两部分 




引用:原帖由 ShadowStar 于 2009-5-18 12:32 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10565192&ptid=1455677]


其实,我觉得,LZ把typedef struct {
        int head;
        int size; //指明整个包的长度
        char reply;
        char data[0];
} packet;

改为typedef struct {
struct {
        i ... 



我觉得以上两位说的挺有道理的,呵呵。


 思一克 回复于:2009-05-18 13:42:54

[QUOTE]
是找到了问题:
sizeof (packet) == 12;
这是合理的,char reply被padding成了4个字节,而char data[0]字节为0。
但,offsetof(packet, data) == 9,在计算偏移时,char reply为一个字节,没有padding。
所以packet_data_len每次都会返回比真实的数据多3个字节 ……
[/QUOTE]
----------------------------
LZ分析的不对。
offsetof(packet, data)=9 说明没有将reply padding为4个字节。


 zx_wing 回复于:2009-05-18 13:51:26

[color=Red][/color]引用:原帖由 思一克 于 2009-5-18 13:42 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10565750&ptid=1455677]

----------------------------
LZ分析的不对。
offsetof(packet, data)=9 说明没有将reply padding为4个字节。 


我在35楼已经讲了这个padding什么时候发生什么时候不发生
引用:
呵呵,data[0]也和前面的紧邻的
如果仅仅是malloc([color=Red]sizeof(packet)),[/color]则data[1]情况变为:
head (4 bytes) size (4 bytes) reply (1 byte) data[1] (1 bytes) padding (2 bytes)
如果是data[0]:
head (4 bytes) size (4 bytes) reply (1 byte) data[0] (0 bytes) padding (3 bytes)

[color=Red]不管data[0],data[1],malloc(sizeof(packet) + 20)后,之后都没有padding[/color]。所以两者内存布局本质是一样的,把data[0]改成data[1]没有任何帮助。




 思一克 回复于:2009-05-18 13:55:20

引用:原帖由 zx_wing 于 2009-5-18 13:51 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10565804&ptid=1455677]

我在35楼已经讲了这个padding什么时候发生什么时候不发生
 



知道了。
反正,你根据offsetof(data)的数值,如果是9,前面的char reply就无padding。就是紧密的。
如果是12, 就是有。


 converse 回复于:2009-05-18 14:33:55

引用:原帖由 ShadowStar 于 2009-5-18 12:32 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10565192&ptid=1455677]


其实,我觉得,LZ把typedef struct {
        int head;
        int size; //指明整个包的长度
        char reply;
        char data[0];
} packet;

改为typedef struct {
struct {
        i ... 



支持这种做法,单独把包头独立出来定义成一个结构体逻辑上也是正确的,这种做法最好不过了.


 zx_wing 回复于:2009-05-18 14:44:00

引用:原帖由 converse 于 2009-5-18 14:33 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10566072&ptid=1455677]


支持这种做法,单独把包头独立出来定义成一个结构体逻辑上也是正确的,这种做法最好不过了. 


前面的定义主要是想说明问题而简化的,实际我的定义中间也嵌套了多个struct,


struct sf_packet_cmd {
    sf_packet_head_t head;
    sf_cmd_t cmd;
    bool_t is_reply;
    char data[0];
};


问题是data前面还有一个变量。除非包装到完全把data独立出来。
写这篇帖子的目的主要是想说明最好还是不要写可能涉及到padding格式的代码,可能在x86下没问题了,换个平台又出问题了。哈哈,算个教训吧。


 darkerwc2 回复于:2009-05-19 01:31:04

:mrgreen: 了解了。


 erwin1984 回复于:2009-05-19 10:25:21

在网络数据传输的情况我一般使用 #pragma pack(1) ... #pragma pack(), 也挺省事的。


 guoruimin 回复于:2009-05-19 10:26:06

不会用,应该搞清楚该怎么用。
而不是,用一种愚蠢的方法解决,误导大家。


 eexplorer 回复于:2009-05-19 13:48:04

引用:原帖由 zx_wing 于 2009-5-17 00:26 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10560715&ptid=1455677]
我想举一个自己最近在项目中犯的错误来说明要踏踏实实做人,不要做装B青年 :(
在代码中,我需要在一个library和一个daemon之间通过socket传送数据包,包的格式定义如下(为了简化,我就用最简单的数据类型举例 ... 



涉及到[0],网络数据交换,一般都需要__attribute__((packed)).


 landylau_ren 回复于:2009-05-19 13:59:57

:outu:  
很naive的一个错误啊  有必要花我这么长时间去看一堆乱七八糟的讨论  
少点争论,有事说事
我还以为有什么高深的技术含量咧 
真是个装13问题


 zx_wing 回复于:2009-05-19 14:01:04

引用:原帖由 eexplorer 于 2009-5-19 13:48 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10571983&ptid=1455677]


涉及到[0],网络数据交换,一般都需要__attribute__((packed)). 


>>__attribute__((packed))
是的,这个是应该的


 zx_wing 回复于:2009-05-19 14:02:59

引用:原帖由 landylau_ren 于 2009-5-19 13:59 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10572080&ptid=1455677]
:outu:  
很naive的一个错误啊  有必要花我这么长时间去看一堆乱七八糟的讨论  
少点争论,有事说事
我还以为有什么高深的技术含量咧 
真是个装13问题 


哈哈,错误是很naive。
光看个sizeof != offsetof确实也没什么技术含量


 fly6 回复于:2009-05-19 15:13:16

在网络数据传输的情况我一般使用 #pragma pack(1) ... #pragma pack(), 也挺省事的。
============================
我用的就是这种方法,楼主是什么方法解决的


 OwnWaterloo 回复于:2009-05-19 16:23:59

引用:原帖由 erwin1984 于 2009-5-19 10:25 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10570590&ptid=1455677]
在网络数据传输的情况我一般使用 #pragma pack(1) ... #pragma pack(), 也挺省事的。 



使用#pragma pack或者 __attribute__((packed))是解决了padding问题。
但是会不会导致新的,未对齐数据访问的问题?


 zx_wing 回复于:2009-05-19 16:27:33

引用:原帖由 OwnWaterloo 于 2009-5-19 16:23 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10573325&ptid=1455677]


使用#pragma pack或者 __attribute__((packed))是解决了padding问题。
但是会不会导致新的,未对齐数据访问的问题? 


在代码中避免使用类似:
*(int*)&a_struct->member = xxx;
的方式,使用memcpy


 OwnWaterloo 回复于:2009-05-19 16:31:12

ok, 又是效率问题了 ……

其实并不是使用memcpy的时候, 而是在写下 packed 的时候, 效率问题就挥之不去了 ……
解决方法还是像楼主所说的那样 ……
一开始就“踏踏实实”写代码~~~  尽可能少玩"花招"~~~

[ 本帖最后由 OwnWaterloo 于 2009-5-19 16:36 编辑 ]


 coneagoe 回复于:2009-05-20 08:04:40

mark一下,提醒自己


 wsw1wsw2 回复于:2009-05-20 15:50:57

#define offsetof(type, element) ((unsigned long)&((type *)0)->element)
==================================
类似这种东西,真的是不敢苟同。Linux Kernel 中的代码我就当看个西洋镜,自己做软件的时候用这种“技巧”不会把自己绕死啊。

针对LZ对数据结构的设计,特别是要与其他进程通讯的数据结构,千万不要省下我们自以为可以省下的信息。

有的时候看人家程序员的代码,感觉很幼稚,++ , -- 的操作做的都十分谨慎,但是程序设计多了,反而发现自己很幼稚。


 peimichael 回复于:2009-05-20 16:23:16

学习了,我自己对padding理解的也一直比较模糊。

这句话有气魄
作为一个装B青年,我认为当然不需要:luya: :luya:


 flyingtime 回复于:2009-05-21 13:59:26

:mrgreen:   好贴,顶起


 yeehya 回复于:2009-05-21 14:30:19

其实对于网络传输,为什么不用pragma pack,不是标准?


 SanZhiYuan 回复于:2009-05-21 14:34:17

求问下那段宏到底罗嗦的什么东西?我用gcc没编译通过:shock: 

error: cast from 'char (*)[0]' to 'int' loses precision


 cathyleung 回复于:2009-05-21 15:07:37

好文~


 OwnWaterloo 回复于:2009-05-21 15:53:09

将指针转型到整数会丢失精度。


 ryan3216 回复于:2009-05-21 21:32:25

装B有理,敢装就要拿出证据。
帖子不错:lol:


 jamesr 回复于:2009-05-25 17:43:56

引用:原帖由 wsw1wsw2 于 2009-5-20 15:50 发表 [url=http://bbs3.chinaunix.net/redirect.php?goto=findpost&pid=10578550&ptid=1455677]
#define offsetof(type, element) ((unsigned long)&((type *)0)->element)
==================================
类似这种东西,真的是不敢苟同。Linux Kernel 中的代码我就当看个西洋镜,自己做软件的时候用 ... 




呵呵!你可以man一下stddef.h。
不是没见过的就是不好的,关键是得知道怎么用。


 syno_snow 回复于:2009-05-26 11:58:45

学习了,就事论事的讨论很吸引人:wink:


 雨过白鹭洲 回复于:2009-05-27 00:29:04

不同机器间的数据交换,除了对齐,还有大端小端等等问题

你定义struct来传报文,当然会出问题了;传二进制数据也同样是不可靠的

感谢楼主分享经验。。


 xiaoQ008 回复于:2009-05-27 02:20:35

晕,看你们俩的技术交流有趣 看到第4页就没看了
也不是很懂


 lbaby 回复于:2009-06-14 21:47:53

网络间传递raw bin 数据,只能说这个想法太让人无语了。


 lbaby 回复于:2009-06-14 21:54:00

引用:原帖由 雨过白鹭洲 于 2009-5-27 00:29 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=10609528&ptid=1455677]
不同机器间的数据交换,除了对齐,还有大端小端等等问题

你定义struct来传报文,当然会出问题了;传二进制数据也同样是不可靠的

感谢楼主分享经验。。 



还是这位兄弟,是解决问题的人,要解决问题,就要学此位兄弟的态度


 xhl 回复于:2009-06-14 23:00:16

很典型的把简单的问题复杂化。

无聊贴。