有符号位域的多个不一致行为

我在签名位字段上遇到了奇怪的行为:

#include <stdio.h>

struct S {
    long long a31 : 31;
    long long a32 : 32;
    long long a33 : 33;
    long long : 0;
    unsigned long long b31 : 31;
    unsigned long long b32 : 32;
    unsigned long long b33 : 33;
};

long long f31(struct S *p) { return p->a31 + p->b31; }
long long f32(struct S *p) { return p->a32 + p->b32; }
long long f33(struct S *p) { return p->a33 + p->b33; }

int main() {
    struct S s = { -2,-2,1,1 };
    long long a32 = -2;
    unsigned long long b32 = 1;
    printf("f31(&s)       => %lld\n",f31(&s));
    printf("f32(&s)       => %lld\n",f32(&s));
    printf("f33(&s)       => %lld\n",f33(&s));
    printf("s.a31 + s.b31 => %lld\n",s.a31 + s.b31);
    printf("s.a32 + s.b32 => %lld\n",s.a32 + s.b32);
    printf("s.a33 + s.b33 => %lld\n",s.a33 + s.b33);
    printf("  a32 +   b32 => %lld\n",a32 +   b32);
    return 0;
}

在OS / X上使用Clang,我得到以下输出:

f31(&s)       => -1
f32(&s)       => 4294967295
f33(&s)       => -1
s.a31 + s.b31 => 4294967295
s.a32 + s.b32 => 4294967295
s.a33 + s.b33 => -1
  a32 +   b32 => -1

在Linux上使用GCC,我得到了:

f31(&s)       => -1
f32(&s)       => 4294967295
f33(&s)       => 8589934591
s.a31 + s.b31 => 4294967295
s.a32 + s.b32 => 4294967295
s.a33 + s.b33 => 8589934591
  a32 +   b32 => -1

上面的输出显示了三种不一致类型:

  • 不同编译器的行为不同;
  • 不同位域宽度的行为不同;
  • 内联表达式和包装在函数中的等效表达式的不同行为。

C标准使用以下语言:

  

6.7.2类型说明符

     

...

     

每个逗号分隔的多集指定相同的类型,但对于位字段,指定符int指定与signed int相同的类型还是指定与unsigned int相同的类型,则由实现定义p=1013; g=3; h=245; G=[g]; a=[1]; b=[0]; a_found=0; def t_next(i,t): if t%3==0: G.append((G[i]*g)%p); elif t%3==1: G.append((G[i]*h)%p); else: G.append((G[i]^2)%p); def a_next(i,t): if t%3==0: a.append((a[i]+1)%p); elif t%3==1: a.append((a[i])%p); else: a.append((2*a[i])%p); def b_next(i,t): if t%3==0: b.append((b[i])%p); elif t%3==1: b.append((b[i]+1)%p); else: b.append((2*b[i])%p); i=1; c=1; while a_found != 1: n=i; for k in range(n,2*n+1): j=k-1; t_next(j,G[j]); a_next(j,G[j]); a b_next(j,G[j]); i+=1; if G[c]==G[2*c]: a_found=1; print [i,G[c],G[2*c],a[c],a[2*c],b[c],b[2*c]] else: c+=1;

众所周知,在许多较旧的编译器中,位字段都被破坏了...
Clang和GCC的行为是一致的还是这些不一致是一个或多个错误的结果?

cszy168 回答:有符号位域的多个不一致行为

请查看建议的代码,它可以正常工作并按预期运行。

出于实际目的,我建议,只要确保

  • 添加了兼容类型,
  • 返回正确的类型并且
  • 正确的类型在 printf 语句中。

就是这样。

欲了解更多信息,另见参考文献[1]和 [2],如下。

#include <stdio.h>

struct S {
    long long a31 : 31;
    long long a32 : 32;
    long long a33 : 33;
    
    unsigned long long b31 : 31;
    unsigned long long b32 : 32;
    unsigned long long b33 : 33;
};

long long f31(struct S *p) { return ((long long)p->a31 + (long long)p->b31); }
long long f32(struct S *p) { return ((long long)p->a32 + (long long)p->b32); }
long long f33(struct S *p) { return ((long long)p->a33 + (long long)p->b33); }

int main() {
    struct S s = { -2,-2,1,1 };
    long long a32 = -2;
    unsigned long long b32 = 1;
    
    printf("p->a31       => %lld\n",(long long)(s.a31));
    printf("p->a32       => %lld\n",(long long)(s.a32));
    printf("p->a33       => %lld\n",(long long)(s.a33));
    
    printf("p->b31       => %lld\n",(long long)(s.b31));
    printf("p->b32       => %lld\n",(long long)(s.b32));
    printf("p->b33       => %lld\n",(long long)(s.b33));
    
    
    printf("f31(&s)       => %lld\n",(long long)(f31(&s)));
    printf("f32(&s)       => %lld\n",(long long)(f32(&s)));
    printf("f33(&s)       => %lld\n",(long long)(f33(&s)));
    printf("s.a31 + s.b31 => %lld\n",((long long)s.a31 + (long long)s.b31));
    printf("s.a32 + s.b32 => %lld\n",((long long)s.a32 + (long long)s.b32));
    printf("s.a33 + s.b33 => %lld\n",((long long)s.a33 + (long long)s.b33));
    printf("  a32 +   b32 => %lld\n",(long long) (a32 +   b32));
    return 0;
}

p->a31       => -2
p->a32       => -2
p->a33       => -2
p->b31       => 1
p->b32       => 1
p->b33       => 1
f31(&s)       => -1
f32(&s)       => -1
f33(&s)       => -1
s.a31 + s.b31 => -1
s.a32 + s.b32 => -1
s.a33 + s.b33 => -1
  a32 +   b32 => -1

参考资料

[1] Signed to unsigned conversion in C - is it always safe?

[2] https://www.geeksforgeeks.org/bit-fields-c/ “我们不能有指向位域成员的指针,因为它们可能不是从字节边界开始的。”

本文链接:https://www.f2er.com/3106398.html

大家都在问