LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1616|回复: 11

位操作问题

[复制链接]
发表于 2008-10-18 20:41:39 | 显示全部楼层 |阅读模式
MP3帧头部格式如下
struct FrameHeader
{
        unsigned sync       : 11;
        unsigned version    : 2;
        unsigned layer      : 2;
        unsigned protection : 1;
        unsigned bitrate    : 4;
        unsigned frequency  : 2;
        unsigned padding    : 1;
        unsigned prvdata    : 1;
        unsigned mode       : 2;
        unsigned modeext    : 2;
        unsigned copyrht    : 1;
        unsigned original   : 1;
        unsigned emphasis   : 2;       
};
能将数据正确读入该结构体,但printf("%X", frmHeader.sync)却出现问题,本来应该是7FF,输出却是3FF,后边各成员输出结果也不对,请哪位大侠解释一下,谢谢!
发表于 2008-10-19 09:53:22 | 显示全部楼层
把 frmHeader 直接按字节流打印一下, 先看看究竟得到了怎样的数据流. 另外, 看一下 sizeof(struct FrameHeader) 的大小吧
  1. unsigned char * p = &frmHeader;
  2. int i;
  3. for (i = 0; i < sizeof(frmHeader); ++i) {
  4.     printf("%02x ", p[i]);
  5. }
  6. printf("\n");
  7. printf("sizeof frmHeader: %d\n", sizeof(frmHeader));
复制代码
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-19 20:11:38 | 显示全部楼层
Post by remote fish;1895961
把 frmHeader 直接按字节流打印一下, 先看看究竟得到了怎样的数据流. 另外, 看一下 sizeof(struct FrameHeader) 的大小吧
  1. unsigned char * p = &frmHeader;
  2. int i;
  3. for (i = 0; i < sizeof(frmHeader); ++i) {
  4.     printf("%02x ", p[i]);
  5. }
  6. printf("\n");
  7. printf("sizeof frmHeader: %d\n", sizeof(frmHeader));
复制代码

大小是4,用这种办法输出结果是对的,FF FB A0 40,转化成二进制,最左边十一位是7FF,但输出frmHeader.sync却是3FF,百思不得其解。
用以下这个例子看比较方便,struct FrameHeader同上。
  1.         unsigned char buf[4];
  2.         struct FrameHeader frmhdr;
  3.         buf[0] = 0xFF; //11111111
  4.         buf[1] = 0xFB; //11111011
  5.         buf[2] = 0xA0;
  6.         buf[3] = 0x40;
  7.         memcpy(&frmhdr, buf, 4);
  8.         printf("%X", frmhdr.sync);
复制代码
结果是3FF!这是为什么?
回复 支持 反对

使用道具 举报

发表于 2008-10-19 23:10:25 | 显示全部楼层
貌似出现了大小端问题. 楼主代码运行的平台是小端的吧. 这方面我没有查相关的文档, 但是我试着运行了一下程序, 根据为 buf 设置不同的数据, 我感觉 gcc 做了一些这样的操作 (以下针对小端系统):
* 尝试按 char/short/int 的顺序, 将连续的位域进行分组
* 组内会出现大小端问题
* 组内各个位域在小端系统上是按照比特序从低向高排列的

比如
  1. struct {
  2.     int a: 3;
  3.     int b: 5;
  4.     int c: 4;
  5. };
复制代码

a 和 b 刚好组合成一个 char 型, 因此在考虑 a, b 顺序时不必考虑 c, 如果 a 和 b 超出了 8 比特, 那么就要与后面的位域结合起来尝试组合成 short, 以至于 int.

a 先定义, 因此 a 从比特位低位开始放, 然后是 b. 按位来写的话, 就是 bbbbb aaa, 刚好与定义顺序相反.

如果是大端系统, 首先可以确定字节序是大端的, 但是不知在比特序上是否也会与小端系统相反. 明天找台大端机器试试.

根据以上经验结论, 我们可以将楼主的 struct 修改成以下形式
  1. struct FrameHeader
  2. {
  3.         //unsigned int  sync : 11;
  4.         unsigned int  sync_h : 8;
  5.         unsigned int  protection : 1;
  6.         unsigned int  layer : 2;
  7.         unsigned int  version : 2;
  8.         unsigned int  sync_l : 3;
  9.         unsigned int  prvdata : 1;
  10.         unsigned int  padding : 1;
  11.         unsigned int  frequency : 2;
  12.         unsigned int  bitrate : 4;
  13.         unsigned int  emphasis : 2;
  14.         unsigned int  original : 1;
  15.         unsigned int  copyrht : 1;
  16.         unsigned int  modeext : 2;
  17.         unsigned int  mode : 2;
  18. };
复制代码
唯一的问题是, 这里由于 sync 域大于 8 比特, 因此在与后面的几个域组合成 short 后, 由于字节序问题, 无法描述. 因此只好把 sync 拆成了两部分, 实际使用时, sync := (sync_h << 3) | sync_l
回复 支持 反对

使用道具 举报

发表于 2008-10-20 12:17:09 | 显示全部楼层
用 mips 开发板试了一下, 在大端环境下, 按照楼主那样写就完全没有问题了, 字节序与比特序都按先高后低的方式进行排列
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-20 15:38:19 | 显示全部楼层
Post by remote fish;1896447
用 mips 开发板试了一下, 在大端环境下, 按照楼主那样写就完全没有问题了, 字节序与比特序都按先高后低的方式进行排列


谢谢remote fish大侠!是大小端的原因。

  1.         unsigned char buf[4];
  2.         struct FrameHeader frmhdr;
  3.         buf[0] = 0xFF; //11111111
  4.         buf[1] = 0xFB; //11111011
  5.         buf[2] = 0xA0;
  6.         buf[3] = 0x40;
  7.         memcpy(&frmhdr, buf, 4);
  8.         printf("%X", frmhdr.sync);
复制代码

按小端模式存放,buf所组成的32位段值实际上是这样的,从高到低 40 A0 FB FF,低16位是11111011 11111111,frmhdr->sync读取的是最低11拉的值,即011 11111111,即3FF!
回复 支持 反对

使用道具 举报

发表于 2008-10-21 10:11:14 | 显示全部楼层
Post by remote fish;1896257
唯一的问题是, 这里由于 sync 域大于 8 比特, 因此在与后面的几个域组合成 short 后, 由于字节序问题, 无法描述. 因此只好把 sync 拆成了两部分, 实际使用时, sync := (sync_h << 3) | sync_l


这其实是不必要的。chaosfun 遇到这个问题是因为他使用位域的方法: 先准备好一块内存再把这块内存中的内容复制进一块结构的内存。因此,为结构类型的变量赋值看似是与大小端相关的。实际上这个赋值是可以与大小端无关的,那就是直接赋值而不是复制:

  1. struct FrameHeader frmhdr;
  2. frmhdr.sync = 0x7ff;
  3. printf ("%x\n", frmhdr.sync);
复制代码


这样打印的结果就是 0x7ff 了。这种写法更具可移植性。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2008-10-21 11:57:45 | 显示全部楼层
herberteuler,你说的办法我试过,那样可以。
这个结构体实际上是MP3帧头部,从文件中读取出来的,本身就是数组,没办法直接赋值。我现在这么做的
将结构体各成员逆序
  1. struct FrameHeader
  2. {
  3.         unsigned emphasis   : 2;
  4.         unsigned original   : 1;
  5.         unsigned copyrht    : 1;
  6.         unsigned modeext    : 2;
  7.         unsigned mode       : 2;
  8.         unsigned prvdata    : 1;
  9.         unsigned padding    : 1;
  10.         unsigned frequency  : 2;
  11.         unsigned bitrate    : 4;
  12.         unsigned protection : 1;
  13.         unsigned layer      : 2;
  14.         unsigned version    : 2;
  15.         unsigned sync       : 11;
  16. };
复制代码
再将frame_header数组逆序
  1. unsigned char *pbyte;
  2. unsigned char  abyte;
  3. unsigned char  frame_header[4];
  4. struct FrameHeader *frmHeader;
  5. fread(frame_header, 4, 1, fd); //头部信息读入frame_header数组
  6. pbyte = frame_header;
  7. abyte = *(pbyte + 3);
  8. *(pbyte + 3) = *pbyte;
  9. *pbyte = abyte; //frame_header[0]与frame_header[3]互换
  10. abyte = *(pbyte + 2);
  11. *(pbyte + 2) = *(pbyte + 1);
  12. *(pbyte + 1) = abyte; //frame_header[1]与frame_header[2]互换
  13. frmHeader = (struct FrameHeader *)frame_header;
复制代码
这种情况下,frmHeader->sync是预想中的值,别的成员也可以正确使用。
回复 支持 反对

使用道具 举报

发表于 2008-10-21 15:49:31 | 显示全部楼层
Post by chaosfun;1896911
再将frame_header数组逆序

  1. unsigned char *pbyte;
  2. unsigned char  abyte;
  3. unsigned char  frame_header[4];
  4. struct FrameHeader *frmHeader;
  5. fread(frame_header, 4, 1, fd); //头部信息读入frame_header数组
  6. pbyte = frame_header;
  7. abyte = *(pbyte + 3);
  8. *(pbyte + 3) = *pbyte;
  9. *pbyte = abyte; //frame_header[0]与frame_header[3]互换
  10. abyte = *(pbyte + 2);
  11. *(pbyte + 2) = *(pbyte + 1);
  12. *(pbyte + 1) = abyte; //frame_header[1]与frame_header[2]互换
  13. frmHeader = (struct FrameHeader *)frame_header;
复制代码

这种情况下,frmHeader->sync是预想中的值,别的成员也可以正确使用。


这段代码完成的工作实际上是将网络字节序转换成本机字节序。为了同时支持两种字节序又使代码比较容易维护,可以分别为两种字节序的机器定义不同的结构,就像你上面那样,两种结构中成员的顺序相反。然后,读取 mp3 帧头时使用整数,并像下面这样来转换:

  1. uint32_t mp3_frame_header;
  2. struct FrameHeader h;
  3. fread (&mp3_frame_header, sizeof mp3_frame_header, 1, fp);
  4. mp3_frame_header = ntohl (mp3_frame_header);
  5. memcpy (&h, &mp3_frame_header, sizeof mp3_frame_header);
复制代码
回复 支持 反对

使用道具 举报

发表于 2008-10-21 19:08:56 | 显示全部楼层
针对某款具体产品的话,也无需考虑移植性
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表