0

假设以下 c++ 代码:

#include <iostream>
using namespace std;

typedef struct 
{
       int a: 5;
       int b: 4;
  int c: 1;
  int d: 22;

} example;

int main()
{
example blah;

blah.a = -5; // 11011
blah.b = -3; // 1101

int result = blah.a << 4 | blah.b;

cout << "Result = " << result << endl; // equals 445 , but I am interested in this having a value of -67 

return 0;
}

我有兴趣让变量 result 是 int 类型,其中第 9 位是最高有效位。我希望是这种情况,以便结果 = -67 而不是 445。这是如何完成的?谢谢。

4

1 回答 1

1

有关密切相关的问题(但不是重复的),请参阅Sign Extending intin C。

您需要注意,几乎所有关于位域的内容都是“实现定义的”。特别是,您是否可以将负数分配给“普通int”位字段尚不清楚;您必须知道您的实现是使用“plain intis signed”还是“plain intis unsigned”。第 9也很棘手;您是从 0 还是 1 开始计数,位域集的哪一端位于位 0,哪一端位于位 31(将最低有效位 (LSB) 计为位 0,将最高有效位 (MSB) 计为位 31) 32 位数量)。实际上,结构的大小不必是 32 位;编译器可能对布局有不同的规则。

排除所有这些注意事项后,您将得到一个 9 位值(blah.a << 4) | blah.b,并且您希望将其符号扩展为将 9 位 2 的补码提升为 (32-bit) int

交叉引用答案中的函数可以完成这项工作:

#include <assert.h>
#include <limits.h>

extern int getFieldSignExtended(int value, int hi, int lo);

enum { INT_BITS = CHAR_BIT * sizeof(int) };
int getFieldSignExtended(int value, int hi, int lo)
{
    assert(lo >= 0);
    assert(hi > lo);
    assert(hi < INT_BITS - 1);
    int bits = (value >> lo) & ((1 << (hi - lo + 1)) - 1);
    if (bits & (1 << (hi - lo)))
        return(bits | (~0 << (hi - lo)));
    else
        return(bits);
}

调用它:

int result = getFieldSignExtended((blah.a << 4) | blah.b), 8, 0);

如果你想硬连线数字,你可以写:

int x = (blah.a << 4) | blah.b;

int result = (x & (1 << 8)) ? (x | (~0 << 8)) : x;

注意我假设第 9位是一个值的第 8 位,其中包含0..8位。如果您有其他解释,请进行调整。


工作代码

g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)从 RHEL 5 x86/64 机器编译。

#include <iostream>
using namespace std;

typedef struct 
{
    int a: 5;
    int b: 4;
    int c: 1;
    int d: 22;
} example;

int main()
{
    example blah;

    blah.a = -5; // 11011
    blah.b = -3; // 1101

    int result = blah.a << 4 | blah.b;

    cout << "Result = " << result << endl;

    int x = (blah.a << 4) | blah.b;
    cout << "x = " << x << endl;

    int result2 = (x & (1 << 8)) ? (x | (~0 << 8)) : x;
    cout << "Result2 = " << result2 << endl;

    return 0;
}

样本输出:

Result = 445
x = 445
Result2 = -67

ISO/IEC 14882:2011 — C++ 标准

§7.1.6.2 简单类型说明符

¶3 ... [注意:char类型和某些位域(9.6)的对象是否表示为有符号或无符号数量是实现定义的。说明signed符强制对 char 对象和位域进行签名;它在其他情况下是多余的。——尾注]

§9.6 位域 [class.bit]

¶1 表单的成员声明符

 identifier<sub>opt</sub> attribute-specifier-seq<sub>opt</sub>: constant-expression

指定一个位域;它的长度由冒号从位域名称开始。可选的属性说明符序列属于被声明的实体。位域属性不是类成员类型的一部分。常量表达式应该是一个整数常量表达式,其值大于或等于零。整数常量表达式的值可能大于位域类型的对象表示(3.9)中的位数;在这种情况下,额外的位被用作填充位并且不参与位域的值表示(3.9)。类对象内的位域分配是实现定义的。位域的对齐是实现定义的。位域被打包到一些可寻址的分配单元中。[ 笔记:位域跨越某些机器上的分配单元,而不是其他机器。位域在某些机器上从右到左分配,在其他机器上从左到右分配。——尾注]

¶2 省略标识符的位域声明声明了一个未命名的位域。未命名的位域不是成员并且不能被初始化。[ 注意:未命名的位域对于填充以符合外部强加的布局很有用。—尾注] 作为一种特殊情况,宽度为零的未命名位域指定下一个位域在分配单元边界处的对齐方式。只有当声明一个未命名的位域时,常量表达式的值才可以等于 0。

¶3位字段不应是静态成员。位域应具有整数或枚举类型(3.9.1)。普通(既不是显式签名也不是无符号)char、short、int、long 或 long long 位字段是有符号还是无符号是实现定义的。bool 值可以成功地存储在任何非零大小的位域中。地址运算符 & 不应应用于位域,因此没有指向位域的指针。非常量引用不应绑定到位字段 (8.5.3)。[ 注意:如果 const T& 类型的引用的初始化器是一个引用位域的左值,则该引用绑定到一个临时初始化以保存位域的值;引用不直接绑定到位域。见 8.5.3。——尾注]

¶4 如果值 true 或 false 存储到任何大小的 bool 类型的位域(包括一位位域),则原始 bool 值和位域的值应比较相等。如果枚举数的值存储在相同枚举类型的位域中,并且位域中的位数大到足以容纳该枚举类型(7.2)的所有值,则原始枚举值和位域的值应比较相等。[ 例子:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
    BOOL b:1;
};
A a;
void f() {
    a.b = TRUE;
    if (a.b == TRUE) // yields true
    { /* ... */ }
}

—结束示例]


ISO/IEC 9899:2011 — C2011 标准

C 标准具有基本相同的效果,但信息的呈现方式有所不同。

6.7.2.1 结构和联合说明符

¶4 指定位域宽度的表达式应为具有非负值的整数常量表达式,该值不超过在省略冒号和表达式时将指定的类型的对象的宽度。122)如果值为零,则声明不应有声明符。

¶5 位字段的类型应为 、 、 或其他一些实现定义的类型的合格或非_Bool合格signed int版本unsigned int。是否允许原子类型是实现定义的。

¶9 ...此外,可以声明一个成员由指定数量的位组成(包括符号位,如果有的话)。这样的成员称为位域;124)它的宽度前面有一个冒号。

¶10 位字段被解释为具有由指定位数组成的有符号或无符号整数类型。125)如果值 0 或 1 存储到 _Bool 类型的非零宽度位域中,则该位域的值应与存储的值进行比较;_Bool 位域具有 _Bool 的语义。

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

¶12 没有声明符但只有冒号和宽度的位域声明表示未命名的位域。126)作为一种特殊情况,宽度为 0 的位域结构成员表示不会将进一步的位域打包到放置前一个位域(如果有的话)的单元中。

122)虽然对象中的位数_Bool至少为CHAR_BIT,但 _Bool 的宽度(符号和值位的数量)可能只有 1 位。

124)一元 & (address-of) 运算符不能应用于位域对象;因此,没有指向位域对象的指针或数组。

125)如上面 6.7.2 所述,如果实际使用的类型说明符是 int 或定义为 int 的 typedef-name,那么位域是有符号还是无符号是实现定义的。

126)未命名的位域结构成员可用于填充以符合外部强加的布局。

该标准的附件 J 定义了可移植性问题,而 §J.3 定义了实现定义的行为。在某种程度上,它说:

J.3.9 结构、联合、枚举和位域

¶1 — ''plain'' int 位域是否被视为有符号 int 位域或无符号 int 位域(6.7.2、6.7.2.1)。

— 除 _Bool、signed int 和 unsigned int (6.7.2.1) 之外的允许位域类型。

— 位域是否允许原子类型 (6.7.2.1)。

— 位域是否可以跨越存储单元边界 (6.7.2.1)。

— 一个单元中位域的分配顺序(6.7.2.1)。

于 2012-12-10T22:31:08.453 回答