C语言 如何使用位运算符检测变量和掩码大小不匹配

8zzbczxx  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(119)

我想检查在C源文件中用于变量的位运算符掩码是否在变量的最小范围内。如果我转换了太大的掩码,我确实会得到一个警告,但是我宁愿不需要转换来触发警告。
换句话说,如果在按位运算中使用的掩码值大于unsigned short(例如0x 80000)中可以包含的值,则在将该掩码值与无符号短整型变量(例如usVal & MY_MASK,其中MY_MASK定义为#define MY_MASK 0x80000)一起使用时,将生成警告或错误。
我意识到unsigned short可能大于0到65,535的最小范围,但我使用的是x86的Visual Studio,32位编译似乎将unsigned short视为16位值。
我可以使用其他的方法,只要它能与Visual Studio 2019一起使用,我会很感激您的建议。可以与Visual Studio一起使用的其他C编译器可能会生成这样的警告或lint类型的实用程序。Visual Studio 2019中也可能有一个源代码分析器设置,它也会标记这样的错误。或者Visual Studio 2022可能会发出这样的警告
似乎 gccclang 有一个-Wconversion标志,最佳和最差的GCC编译器标志为嵌入式:-Wconversion,以警告隐式转换,那么有没有类似的东西为Visual Studio 2019?这会捕捉到这种类型的问题?
在Visual Studio 2019中,我可以有以下C源代码:

typedef unsigned long ULONG;
typedef unsigned short USHORT;

#define KPS_MASK16_ALT_MAN   (ULONG)0x80000
#define KPS_MASK16_ALT_AUTO  0x4000

#define MASK_CHECK(t,v,op,m)  ((v) op (t)(m))

int    KpsIsAlt1(USHORT usOutPrinterInfo) {
    return  (usOutPrinterInfo & (USHORT)KPS_MASK16_ALT_MAN) || (usOutPrinterInfo & (USHORT)KPS_MASK16_ALT_AUTO);
}

int    KpsIsAlt2(USHORT usOutPrinterInfo) {
    return  (usOutPrinterInfo & KPS_MASK16_ALT_MAN) || (usOutPrinterInfo & KPS_MASK16_ALT_AUTO);
}

int    KpsIsAlt3(USHORT usOutPrinterInfo) {
    return  (MASK_CHECK(USHORT,usOutPrinterInfo, &, KPS_MASK16_ALT_MAN) || (usOutPrinterInfo & KPS_MASK16_ALT_AUTO));
}

字符串
这将使用警告级别3或4生成以下编译器警告,其中第136行是函数KpsIsAlt1()中的掩码操作,第145行是使用define MASK_CHECK的函数KpsIsAlt3()中的掩码操作:

1>Source2.c
1>D:\Users\rickc\Documents\vs2019repos\ConsoleApplicationClosure\Source2.c(136,38): warning C4310: cast truncates constant value
1>D:\Users\rickc\Documents\vs2019repos\ConsoleApplicationClosure\Source2.c(145,11): warning C4310: cast truncates constant value
1>ConsoleApplicationClosure.vcxproj -> D:\Users\rickc\Documents\vs2019repos\ConsoleApplicationClosure\Debug\ConsoleApplicationClosure.exe
1>Done building project "ConsoleApplicationClosure.vcxproj".


我希望在函数KpsIsAlt2()的第141行也能看到一些关于掩码的警告,但是没有任何关于掩码值超出变量范围的警告。
如果将遮罩与型别太小而无法与该遮罩一起使用的变数一起使用,Visual Studio似乎不会发出警告。就好像编译器正在无消息地将unsigned short变数提升为unsigned long变数,并执行遮罩。
我希望看到的是一个“位操作超出范围”的警告,或者是其他类型的提示,即位操作使用的掩码不会测试变量中的任何位。
我尝试了MASK_CHECK() define作为一个可能的解决方案的测试,但是这样做真的没有意义,而不仅仅是使用KpsIsAlt1()函数中使用的类型转换。除非有某种方法让预处理器识别变量类型,否则它真的不实用。
我还做了一个快速测试,将defines替换为:

//#define KPS_MASK16_ALT_MAN   (ULONG)0x80000
//#define KPS_MASK16_ALT_AUTO  0x4000

ULONG const KPS_MASK16_ALT_MAN = 0x80000;
USHORT const KPS_MASK16_ALT_AUTO = 0x4000;


却没有看到任何警告。
如果我更改为enum,我会得到与使用defines时相同的警告。

//#define KPS_MASK16_ALT_MAN   (ULONG)0x80000
//#define KPS_MASK16_ALT_AUTO  0x4000

//ULONG const KPS_MASK16_ALT_MAN = 0x80000;
//USHORT const KPS_MASK16_ALT_AUTO = 0x4000;

enum { KPS_MASK16_ALT_MAN = 0x80000, KPS_MASK16_ALT_AUTO = 0x4000 };

cotxawn7

cotxawn71#

这就好像编译器默默地将unsigned short变量提升为unsigned long变量,并进行掩码。
几乎。它将unsigned short值提升为int,然后在应用&运算符之前将其转换为unsigned long
你看到的是整数提升通常的算术转换的结果。
对于前者,一般来说,在表达式中使用小于int的整数变量或表达式的任何地方,它首先被提升为intunsigned int
这在C standard的第6.3.1.1p2节中详细说明:
在表达式中可以使用in t或unsigned int

  • 具有整数类型(intunsigned int除外)的对象或表达式,其整数转换秩小于或等于intunsigned int的秩。
  • 类型为_Boolintsigned intunsigned int的位字段。

如果int可以表示原始类型的所有值(对于位字段,受宽度限制),则该值被转换为int;否则,它被转换为unsigned int。这些被称为 * 整数提升 *。所有其他类型都不会被整数提升所改变。
在后一种情况下,当一个操作符有两个不同类型的操作数,其中一个小于另一个时,具有较小类型的值被转换为较大类型。这在6.3.1.8p1节中规定(见粗体部分):
.整数提升在两个操作数上执行。然后将以下规则应用于提升的操作数:

  • 如果两个操作数具有相同的类型,则不需要进一步的转换。
  • 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换秩的类型的操作数被转换为具有较大秩的操作数的类型。
    *否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则具有有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。
  • 否则,如果具有有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数的类型的所有值,则具有无符号整数类型的操作数被转换为具有有符号整数类型的操作数的类型。
  • 否则,两个操作数都转换为与带符号整数类型的操作数的类型对应的无符号整数类型

在这个表达式中:

usOutPrinterInfo & KPS_MASK16_ALT_MAN

字符串
usOutPrinterInfo的值首先提升为int,然后转换为unsigned long(因为这是KPS_MASK16_ALT_MAN的类型),结果的类型为unsigned long
这意味着这种行为是预期的,对于你正在做的事情,你唯一的选择就是应用一个类型转换,最好是在定义常量的地方,这样你就只需要在一个地方做。

相关问题