【笔记1】关于C语言中对非符号数进行逐位取反(~)的重要问题
话不多说,先放上代码。
话不多说,先放上代码
#include <stdio.h>
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
}
1.重要的一个运算
c = ~a
-
~a的结果是4294967235(1111_1111_1111_1111_1111_1111_1100_0011),这是一个无符号整数。 -
将
4294967235赋值给int类型的c,由于4294967235超出了int的范围,会发生截断,结果为-61。 -
使用
%d输出c,结果是-61。
2. 截断过程的详细解释
当将 unsigned int 类型的值赋值给 int 类型的变量时,C 语言会进行 隐式类型转换。具体过程如下:
(1) 二进制表示的复制
-
unsigned int的二进制表示会直接复制到int的存储空间中。 -
例如,
c = ~a;会将1111_1111_1111_1111_1111_1111_1100_0011直接复制到c的存储空间中。
(2) 符号位的解释
-
在
int类型中,最高位是符号位:-
如果符号位是
0,则表示正数。 -
如果符号位是
1,则表示负数。
-
-
1111_1111_1111_1111_1111_1111_1100_0011的最高位是1,因此它被解释为一个负数。
(3) 补码的解释
-
在
int类型中,负数使用 补码 表示。 -
补码的计算规则是:取反后加 1。
-
对于
1111_1111_1111_1111_1111_1111_1100_0011,其补码计算如下:-
取反:
0000_0000_0000_0000_0000_0000_0011_1100。 -
加 1:
0000_0000_0000_0000_0000_0000_0011_1101。
-
-
这个二进制数对应的十进制值是
61。 -
由于符号位是
1,因此最终结果是-61。 3.STM32开发中对应的实际问题 - 在 STM32 单片机软件开发 中,类型转换 和 截断问题 是非常常见的,尤其是在处理硬件寄存器、位操作、数据存储和通信协议时。由于嵌入式系统对内存和性能的要求较高,开发者通常会使用更小的数据类型(如
uint8_t、uint16_t)来节省资源,这增加了类型不匹配和截断问题的风险。 -
(1)硬件寄存器操作
STM32 的硬件寄存器通常是以 无符号整数 类型定义的(如
uint32_t)。如果开发者错误地使用有符号整数(如int)来操作这些寄存器,可能会导致意外的行为。例子:配置 GPIO 寄存器
uint32_t *GPIO_MODER = (uint32_t*)0x40020000; // GPIO 模式寄存器地址 int mode = 0xFFFF; // 错误:使用有符号整数 *GPIO_MODER = mode; // 可能截断或符号扩展 -
如果
mode是一个负数,赋值给GPIO_MODER时会被解释为一个非常大的无符号整数,导致 GPIO 配置错误。 -
解决方法:
-
使用正确的无符号类型:
uint32_t mode = 0xFFFF; // 正确:使用无符号整数 *GPIO_MODER = mode; -
(2)位操作
在 STM32 开发中,位操作(如
&、|、~、<<、>>)非常常见,用于配置寄存器或提取数据。如果操作数的类型不匹配,可能会导致截断或符号扩展问题。例子:读取 ADC 数据
uint16_t adc_value = ADC1->DR; // 从 ADC 数据寄存器读取值 int processed_value = adc_value >> 4; // 右移操作 -
如果
adc_value的最高位是1,右移操作可能会导致符号扩展(即高位填充1),从而得到一个负数。 -
解决方法:
-
使用无符号类型进行位操作:
uint16_t adc_value = ADC1->DR; // 从 ADC 数据寄存器读取值 int processed_value = adc_value >> 4; // 右移操作 -
(3) 数据存储与通信
在存储数据或通过通信协议(如 UART、I2C、SPI)传输数据时,如果数据类型不匹配,可能会导致数据截断或解释错误。
例子:UART 数据传输
uint8_t buffer[2]; uint16_t data = 0xABCD; // 16 位数据 buffer[0] = data >> 8; // 高字节 buffer[1] = data & 0xFF; // 低字节 -
如果错误地使用有符号整数:
int data = 0xABCD; // 错误:使用有符号整数 buffer[0] = data >> 8; // 可能符号扩展 buffer[1] = data & 0xFF; // 低字节当
data是负数时,右移操作会导致符号扩展,buffer[0]的值可能不正确。 -
解决方法:
-
始终使用无符号整数处理数据:
uint16_t data = 0xABCD; // 正确:使用无符号整数 buffer[0] = data >> 8; buffer[1] = data & 0xFF; -
(4)数学运算
在嵌入式系统中,数学运算(如加法、乘法)的结果可能会超出目标类型的范围,导致截断或溢出。
例子:计算定时器周期
uint32_t clock_freq = 72000000; // 72 MHz uint32_t prescaler = 72; uint32_t period = clock_freq / prescaler; // 计算周期 int target_period = period; // 错误:可能截断 -
如果
period的值超出int的范围,赋值给target_period时会被截断。 -
解决方法:
-
使用一致的无符号类型:
uint32_t target_period = period; // 正确:使用无符号整数 -
5. 函数参数与返回值
在调用库函数或自定义函数时,如果参数或返回值的类型不匹配,可能会导致截断问题。
例子:HAL 库函数
uint16_t adc_value = HAL_ADC_GetValue(&hadc1); // 读取 ADC 值 int processed_value = adc_value * 2; // 错误:可能溢出 -
如果
adc_value的值较大,adc_value * 2可能会超出int的范围。 -
解决方法:
-
使用更大的数据类型或强制类型转换:
uint32_t processed_value = (uint32_t)adc_value * 2; // 正确:使用更大的类型 -
6. 枚举类型
在 STM32 开发中,枚举类型常用于表示状态或选项。如果枚举值被错误地赋值给有符号整数,可能会导致问题。
例子:状态机
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_ERROR } State_t; State_t state = STATE_IDLE; int current_state = state; // 错误:枚举值可能被解释为负数解决方法:
-
使用正确的枚举类型:
State_t current_state = state; // 正确:使用枚举类型
更多推荐



所有评论(0)