如何以正确的方式在C中以二进制方式从struct打印位域?

时间:2018-08-19 17:07:08

标签: c valgrind bit-fields

我有一个带有多个位域的结构,每个字段必须在长度为14位的“变量”中定义一些不同的内容,在过程结束时,我需要像在二进制文件中打印一个变量那样来构造该结构,我发现了某种方法,创建另一个14位的结构,像麝香一样使用它,就可以了,但是valgrind说 有条件的跳跃或移动取决于未初始化的值 有更好的打印方法吗? 仅使用8位的示例,但不能使用char(实际上需要14位)

    #define MAX 8
typedef struct varNode{
    unsigned int s1:2;
    unsigned int s2:4;
    unsigned int s3:2;
}var;


void printNode(var* node){

    typedef struct {
        unsigned data:MAX;
    }mask;

    mask temp={0};
    temp.data=temp.data|((node->s1)|(node->s2<<2)|(node->s3<<6));
    unsigned x;
    x=1;
    x=x<<(unsigned )(MAX-1);
    while(x) {
        if (temp.data & x )
            printf("1");
        else printf("0");
        x>>=1;
    }
    printf("\n");
}

void main(){
    var a={1,2,3};
    printNode(&a);
}

此处:

temp.data=temp.data|((node->s1)|(node->s2<<2)|(node->s3<<6));

编译器警告我有关带二进制运算符的有符号整数操作数,不明白为什么,节点中的所有字段均为无符号

3 个答案:

答案 0 :(得分:1)

您可以使用联合。

#include <stdio.h>
union varNode {
    struct {
       unsigned int s1:2;
       unsigned int s2:4;
       unsigned int s3:2;
   } bf;
   unsigned int num;
} var;
int main (void)
{
    // Manipulate bitfields
    var.bf.s1 = 3;
    var.bf.s3 = 1;
    // Print all of them
    printf("%d\n", var.num);
    return 0;
}

坏消息:C标准将位域顺序留给编译器实现。结果例如低端飞思卡尔HCS12架构上的图像不适合小孩子观看。

我向您保证,您的代码最终将一起打印所有位域,因为这是唯一有效的调试方法。

最好不要完全忘记位域,而只使用整数和掩码。

答案 1 :(得分:0)

位字段是错误的且不可移植。围绕它们的几乎所有内容(存储顺序,宽度等)都是“实现定义的”。

6.7.2.1 Structure and union specifiers, paragraph 11 of the C standard状态:

  

实现可以分配任何足够大的可寻址存储单元以容纳位字段。如果有足够的空间,则应将紧随结构中另一个位域之后的位域打包到同一单元的相邻位中。如果剩余空间不足,则将实现不适当的位字段放入下一个单元还是与相邻单元重叠。单位内的位域分配顺序(从高位到低位或从低位到高位)由实现定义。未指定可寻址存储单元的对齐方式。

因此,您不知道位在结构中的最终位置,在确定位在哪里时,如果您使用其他编译器进行编译,则可能会得到不同的结果。

要增加反常性,移位位域必须遵循6.3.1.1 Boolean, characters, and integers of the C standard的规则:

  

每个整数类型的整数转换等级定义如下:

     
      
  • 即使两个符号整数类型具有相同的表示形式,也不应具有相同的等级。
  •   
  • 有符号整数类型的等级应大于精度较低的任何有符号整数类型的等级。
  •   
  • long long int的等级应大于long int的等级,后者应大于int的等级,该等级应为   大于short int的等级,该等级应大于   签名字符的等级。
  •   
  • 任何无符号整数类型的等级应等于相应的有符号整数类型的等级(如果有)。
  •   
  • 任何标准整数类型的等级应大于宽度相同的任何扩展整数类型的等级。
  •   
  • char的等级应等于有符号的char和未签名的char的等级。
  •   
  • _Bool的等级应小于所有其他标准整数类型的等级。
  •   
  • 任何枚举类型的等级应等于兼容整数类型的等级(见6.7.2.2)。
  •   
  • 任何扩展的带符号整数类型相对于另一个具有相同精度的扩展的带符号整数类型的等级为   实施定义,但仍然要遵守其他规则   确定整数转换排名。
  •   
  • 对于所有整数类型T1,T2和T3,如果T1的等级高于T2,并且T2的等级高于T3,则T1的等级高于T3。
  •   
     

以下表达式可以在表达式中使用int或unsigned   可以使用int:

     
      
  • 具有整数类型(整数或无符号整数)的对象或表达式,其整数转换等级小于或等于   int和unsigned int的等级。
  •   
  • _Bool,int,signed int或unsigned int类型的位字段。
  •   
     

如果一个int可以表示原始类型的所有值(受位字段的宽度限制),则该值将转换为   ;否则,它将转换为unsigned int。这些是   称为整数促销。所有其他类型均未更改   整数促销。

Footnote 58状态:

  

58)整数促销仅适用:通常的一部分   算术转换,某些参数表达式,到   一元+,-和〜运算符的操作数,以及   移位运算符,如其各自的子句所指定。

请阅读并告诉我位域在整数类型的范围内-这也是“实现定义的”吗?如果是这样,那将使诸如向左或向右移动位域的事情更加容易出错。 (我怀疑这会在Stackoverflow上引起一个很好的语言律师问题-我已经看了几个小时了,我不确定...)

朋友不允许朋友在代码中使用位字段。

答案 2 :(得分:0)

您将数据转换为char数组,并逐字节打印。

struct varNode {
    int s1 : 2;
    int s2 : 4;
    int s3 : 2;
};

void print_byteByByte(void *pnt0, size_t size)
{
    unsigned char *pnt = pnt0;
    while (size--) {
        printf("%02x", *pnt++);
    }
}

int main() {
    struct varNode var; 
    var.s1 = 1;
    var.s2 = 2;
    var.s3 = 3;

    print_byteByByte(&var, sizeof(var));
    printf("\n");
    return 0;
}

这不会违反任何严格的别名规则,因为char可能会别名其他任何类型。

关于您的代码:
signed integer operand with binary operator-您可以将位域声明为_Bool signed intunsigned int,但是它们始终具有“实现定义的类型”,这意味着在体系结构中进行了签名。也许通过强制转换来静默警告? temp.data=temp.data|(((unsigned)node->s1)|((unsigned)node->s2<<2)|((unsigned)node->s3<<6));使用任何gcc -pedantic -Wall -Wextra选项都无法收到任何警告。
Conditional jump or move depends on uninitialised value(s)-我想这是一个正值,我看不到使用任何未初始化的值。我的带有--track-origins=yes的valgrind-3.13.0没有打印警告。