【问题标题】:left shift count >= width of type in C macro左移计数> = C宏中的类型宽度
【发布时间】:2024-01-22 16:20:02
【问题描述】:

我编写了一个 C 宏来设置/取消设置 uint32 变量中的位。以下是宏的定义:

extern uint32_t error_field, error_field2;
    #define SET_ERROR_BIT(x) do{\
                                if(x < 0 || x >63){\
                                    break;\
                                }\
                                if(((uint32_t)x)<32U){\
                                    (error_field |= ((uint32_t)1U << ((uint32_t)x)));\
                                    break;\
                                } else if(((uint32_t)x)<64U){\
                                    (error_field2 |= ((uint32_t)1U<<(((uint32_t)x)-32U)));\
                                }\
                            }while(0)

    #define RESET_ERROR_BIT(x) do{\
                                if(((uint32_t)x)<32U){\
                                    (error_field &= ~((uint32_t)1U<<((uint32_t)x)));\
                                    break;\
                                } else if(((uint32_t)x) < 64U){\
                                    (error_field2 &= ~((uint32_t)1U<<(((uint32_t)x)-32U)));\
                                }\
                             } while(0)

我正在传递一个枚举字段,如下所示:

enum error_bits {
    error_chamber01_data = 0,
    error_port21_data,
    error_port22_data,
    error_port23_data,
    error_port24_data,
/*this goes on until 47*/
};

产生此警告:

左移计数 >= [-Wshift-count-overflow] 类型的宽度

我这样调用宏:

USART2->CR1 |= USART_CR1_RXNEIE;
SET_ERROR_BIT(error_usart2);
/*error_usart2 is 47 in the enum*/
return -1;

每个宏都会收到此警告,即使是左移计数

如果我在没有宏的情况下使用宏的定义,它不会产生任何警告。行为与 64 位变量相同。我正在使用 AC6 STM32 MCU GCC 编译器对 STM32F7 进行编程。 我不明白为什么会这样。谁能帮帮我?

【问题讨论】:

  • 显示宏的调用位置
  • 我已经编辑了帖子。
  • 这个(我真的不是在这里要冒犯的意思)真正可怕的野兽是一个典型的例子,说明为什么宏对于除了简单的 true 之外的任何东西都是一个坏主意/现代 C 编译器中的虚假内容。您可以使用通常几乎没有 性能影响(如果有)的函数来编写 更具可读性的内容。我总是首先尝试优化可读性:-)
  • 我已经想过为此编写一个函数,但我想弄清楚为什么会产生警告
  • 在宏中,您区分两种情况,它们本身是可以的。警告来自未执行的分支,其中班次超出范围。 (显然,这些诊断是在消除死分支之前发出的。)

标签: c gcc macros stm32


【解决方案1】:

可能是编译器无法正确诊断的问题,如 M Oehm 所述。一种解决方法是,不使用减号运算,而是使用余数运算:

#define _SET_BIT(x, bit) (x) |= 1U<<((bit) % 32U)
#define SET_BIT(x, bit) _SET_BIT(x, (uint32_t)(bit))
#define _SET_ERROR_BIT(x) do{\
                            if((x)<32U){\
                                SET_BIT(error_field, x);\
                            } else if((x)<64U){\
                                SET_BIT(error_field2, x);\
                            }\
                        }while(0)
#define SET_ERROR_BIT(x) _SET_ERROR_BIT((uint32_t)(x))

这样编译器终于足够聪明,知道x的值永远不会超过32。

对“_”宏的调用用于强制 x 始终为 uint32_t,无条件地对宏调用,避免使用负值 x 的调用的 UB。

coliru测试

【讨论】:

  • 这适用于数字 =32 的数字,它会产生相同的警告:coliru.stacked-crooked.com/a/3d79a452e4a6efda
  • 是的,因为其他分支也需要取模,我更新链接
  • 我还简化了宏,删除了不必要的强制转换和中断
  • 我找出了错误,请参阅最后一条评论。谢谢!
  • 是的,-1LL 是真的,没有看到。仍然存在漏洞,但在一个新的、不太可能的地方,例如 SET_ERROR_BIT(-0x100000000),因为(uint32_t) 演员只是简单地包装了负值,然后一些最终会变成 0-63。
【解决方案2】:

问题:

在宏中,您可以区分两种情况,它们本身是可以的。警告来自未执行的分支,其中班次超出范围。 (显然这些诊断是在死分支被消除之前发出的。) @M Oehm

解决方案

无论x 的值x 的类型,确保两个路径中的班次都在0-31 范围内。

x &amp; 31x%32x%32u 更保险。当x &lt; 0 和足够宽的类型时,% 可能会导致负余数。

   #define SET_ERROR_BIT(x) do{\
                                if((x) < 0 || (x) >63){\
                                    break;\
                                }\
                                if(((uint32_t)x)<32U){\
                                    (error_field |= ((uint32_t)1U << ( (x)&31 )));\
                                    break;\
                                } else if(((uint32_t)x)<64U){\
                                    (error_field2 |= ((uint32_t)1U<<( (x)&31 )));\
                                }\
                            }while(0)

作为一般规则:在每次使用 x 时最好使用 ()

【讨论】:

    【解决方案3】:

    看到线程,我想指出一个很好的(也许是更干净的)方法来设置、重置和切换位的状态,在线程中的两个无符号整数的情况下。此代码应该是 OT,因为使用的 x 应该是 unsigned int(或 int)而不是 enum 值。

    我已经在这个答案的末尾写了这行代码。

    代码接收多个参数对作为输入。每对参数是一个字母和一个数字。这封信可能是:

    • S 设置位
    • R 重置位
    • T 切换一下

    数字必须是从 0 到 63 的位值。代码中的宏会丢弃每个大于 63 的数字,并且不会对变量进行任何修改。没有对负值进行评估,因为我们假设位值是无符号值。

    例如(如果我们将程序命名为 bitman):

    执行:bitman S 0 S 1 T 7 S 64 T 7 S 2 T 80 R 1 S 63 S 32 R 63 T 62

    输出将是:

    S 0 00000000-00000001
    S 1 00000000-00000003
    T 7 00000000-00000083
    S 64 00000000-00000083
    T 7 00000000-00000003
    S 2 00000000-00000007
    T 80 00000000-00000007
    R 1 00000000-00000005
    S 63 80000000-00000005
    S 32 80000001-00000005
    R 63 00000001-00000005
    电话 62 40000001-00000005

    #include <unistd.h>
    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    
    static uint32_t err1 = 0;
    static uint32_t err2 = 0;
    
    #define SET_ERROR_BIT(x) (\
        ((unsigned)(x)>63)?err1=err1:((x)<32)?\
        (err1 |= (1U<<(x))):\
        (err2 |= (1U<<((x)-32)))\
        )
    
    #define RESET_ERROR_BIT(x) (\
        ((unsigned)(x)>63)?err1=err1:((x)<32)?\
        (err1 &= ~(1U<<(x))):\
        (err2 &= ~(1U<<((x)-32)))\
        )
    
    #define TOGGLE_ERROR_BIT(x) (\
        ((unsigned)(x)>63)?err1=err1:((x)<32)?\
        (err1 ^= (1U<<(x))):\
        (err2 ^= (1U<<((x)-32)))\
        )
    
    int main(int argc, char *argv[])
    {
        int i;
        unsigned int x;
    
        for(i=1;i<argc;i+=2) {
            x=strtoul(argv[i+1],NULL,0);
    
            switch (argv[i][0]) {
            case 'S':
                SET_ERROR_BIT(x);
                break;
            case 'T':
                TOGGLE_ERROR_BIT(x);
                break;
            case 'R':
                RESET_ERROR_BIT(x);
                break;
            default:
                break;
            }
    
            printf("%c %2d %08X-%08X\n",argv[i][0], x, err2, err1);
        }
    
        return 0;
    }
    

    宏被分成多行,但它们都是一行代码。

    代码 main 没有错误控制,如果参数没有正确指定,程序可能是未定义的行为。

    【讨论】:

    • OP 的代码已处理 x &lt; 0。这个答案和first one 一样,在x &lt; 0 时存在漏洞。
    • 避免x
    • 不同意“因为输入变量在编译时应被视为无符号”。枚举值不一定是 unsigned ref 并且 OP 明智地希望通过 if(x &lt; 0 防止可能性
    • 在我实现的代码中,变量 x 是无符号的。可能我是OT。
    最近更新 更多