这个问题的动机是我在C/C中实现加密算法(例如SHA-1),编写与平台无关的可移植代码,并彻底避免未定义的行为。
假设一个标准化的加密算法要求您实现:
b = (a << 31) & 0xFFFFFFFF
其中a
和b
是无符号32位整数。请注意,在结果中,我们丢弃了最低有效32位以上的任何位。
作为第一个简单的近似值,我们可以假设在大多数平台上,int
的宽度为32位,因此我们将编写:
unsigned int a = (...);
unsigned int b = a << 31;
我们知道这段代码不会在任何地方都有效,因为在某些系统上,int
是16位宽,在其他系统上是64位宽,甚至可能是36位宽。但是使用stdint。h
,我们可以使用uint32\u t
类型改进此代码:
uint32_t a = (...);
uint32_t b = a << 31;
我们结束了,对吗?这就是我多年来的想法。。。不完全是。假设在某个平台上,我们有:
// stdint.h
typedef unsigned short uint32_t;
在C/C中执行算术操作的规则是,如果类型(例如短
)比int
窄,那么如果所有值都可以匹配,则将其扩展为int
,否则将扩展为un符号int
。
假设编译器将短
定义为32位(有符号),将int
定义为48位(有符号)。然后这些代码行:
uint32_t a = (...);
uint32_t b = a << 31;
将有效地意味着:
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
请注意,a
被提升为int
,因为所有ushort
(即uint32
)都适合int
(即int48
)。
但是现在我们有一个问题:将非零位向左移动到有符号整数类型的符号位是未定义的行为。这个问题的发生是因为我们的uint32
被提升到int48
-而不是提升到uint48
(在那里左移是可以的)。
以下是我的问题:
>
我的推理正确吗,这在理论上是一个合理的问题吗?
因为在每个平台上,下一个整数类型都是宽度的两倍,所以忽略这个问题安全吗?
通过像这样预先掩蔽输入来正确防御这种病理情况是一个好主意吗<代码>b=(a)
澄清/编辑:
>
我接受C或C或两者的答案。我想知道至少一种语言的答案。
预掩蔽逻辑可能会影响位旋转。例如,GCC将编译b=(a
从这个关于uint32*uint32
算术中可能的UB的问题中获得线索,以下简单的方法应该适用于C和C:
uint32_t a = (...);
uint32_t b = (uint32_t)((a + 0u) << 31);
整数常量0u
的类型为无符号int
。这会将添加a 0u
提升到uint32_t
或无符号int
,以较宽者为准。因为该类型的级别为int
或更高,所以不会发生更多的提升,并且可以在左操作数为uint32_t
或无符号int
的情况下应用移位。
最后一次返回到uint32_t
只会抑制关于变窄转换的潜在警告(比如int
为64位)。
一个好的C编译器应该能够看到添加零是无操作的,这比看到前置掩码在无符号转移后没有效果要简单得多。
问题1:轮班前的掩蔽确实可以防止OP担心的未定义行为。
问题2:“……因为在每个平台上,下一个整数类型都是宽度的两倍?”--
对于所有具有uint32\u t
的兼容C编译器,以下内容都有很好的定义。
uint32_t a;
uint32_t b = (a & 1) << 31;
Q3:<代码>uint32_t;uint32_tb=(a
正如建议的那样,最好强调这些变化的无符号性。
uint32_t b = (a & 1U) << 31;
@John Bollinger好答案详细说明了如何处理OP的具体问题。
通常的问题是如何形成一个至少有n
位,具有一定符号性且不会受到令人惊讶的整数提升的数字——这是OP困境的核心。下面通过调用一个不改变值的无符号
操作来实现这一点——除了类型问题之外,这是一个有效的无操作。乘积至少是无符号
或uint32_t
的宽度。一般来说,强制转换可能会缩小类型。除非强制不会出现强制转换,否则需要避免强制转换。优化编译器不会创建不必要的代码。
uint32_t a;
uint32_t b = (a + 0u) << 31;
uint32_t b = (a*1u) << 31;
说到问题的C面,
是我之前没有考虑过的问题,但是我同意你的分析。C定义了的行为
C不需要整数类型之间的这种关系,尽管它在实践中无处不在。然而,如果您决心只依赖标准——也就是说,如果您不厌其烦地编写严格符合标准的代码——那么您就不能依赖这种关系。
类型
无符号长
保证至少有32个值位,并且在整数提升下不会被提升到任何其他类型。在许多常见平台上,它与uint32_t
具有完全相同的表示形式,甚至可能是相同的类型。因此,我倾向于这样写表达式:
uint32_t a = (...);
uint32_t b = (unsigned long) a << 31;
或者,如果您只需要
。a
作为b
计算中的中间值,则首先将其声明为无符号长
我不确定安德烈·卡隆在这里是什么意思: C中的虚拟函数 ...其中一些代码依赖于(正式的)非标准行为,这些行为“恰好”在大多数编译器上工作。主要问题是代码假设 < code>m的类型为< code>struct meh *。类型为< code>struct foo *的对象f通过向< code>struct meh *的强制转换被赋给m。< code>struct meh具有< code>stru
本文向大家介绍C#中实现屏蔽Ctrl+C的方法,包括了C#中实现屏蔽Ctrl+C的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#实现屏蔽Ctrl+C的方法,代码简单易懂,具有一定的实用价值。分享给大家供大家参考。具体方法如下: 主要实现方法为重写 WndProc,代码如下: 希望本文所述C#实例对大家有所帮助。
我需要用c语言验证由JAVA签名API生成的签名。我有一套相同的公钥和私钥。我还验证了c语言的签名和验证工作。但是我在验证JAVA生成的签名时遇到了问题。我看了所有的文件,尝试了不同的方法,但我似乎还是不明白。我将使用用于验证的原始JAVA代码粘贴等效的c代码。 原始JAVA代码: C代码: 我有3个问题: 这是验证签名的正确方法吗? 我需要在验证数据之前对其进行散列吗?如果是,那么JAVA在签名
主要内容:基类成员函数和派生类成员函数不构成重载如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。 下面是一个成员函数的名字遮蔽的例子: 运行结果: 小明的年龄是16,成绩是90.5 嗨,大家好,我叫小明,今年16岁 本例中,基类 People 和派
我有一个使用C++中的openSSL生成的keypair,我正在使用它在一个严格使用RSACryptoServiceProvider(没有BouncyCastle等)的C#服务器上对验证消息进行签名。我使用PKCS#1 SHA256生成签名,然后以十六进制的形式与公钥一起传输签名。问题是无法在服务器上验证签名。我已经尝试删除了头,上面写着“----开始RSA公钥-----”等等。但是还没有结果。生
下面是C 17形式的规则([basic.lval]/8),但它在其他标准中看起来很相似(C 98中是“lvalue”而不是“glvalue”): 8如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义: (8.4)-对应于对象动态类型的有符号或无符号类型 这条规则听起来像是“除非你做X,否则你会得到UB”,但这并不意味着如果你做了X,你就不会得到UB,正如人们所期望的那样