【问题标题】:Fix for dereferencing type-punned pointer will break strict-aliasing修复取消引用类型双关指针将破坏严格混叠
【发布时间】:2012-02-08 02:59:40
【问题描述】:

我正在尝试在使用 GCC 编译特定程序时修复两个警告。警告是:

警告:取消引用类型双关指针会中断 严格别名规则 [-Wstrict-aliasing]

两个罪魁祸首是:

unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

incoming_bufoutgoing_buf 定义如下:

char                    incoming_buf[LIBIRC_DCC_BUFFER_SIZE];

char                    outgoing_buf[LIBIRC_DCC_BUFFER_SIZE];

这似乎与我一直在研究的其他警告示例略有不同。我宁愿解决问题,也不愿禁用严格别名检查。

有很多使用联合的建议 - 哪种联合可能适合这种情况?

【问题讨论】:

  • 有趣...严格别名不应该适用于char*。还是我错过了什么?
  • @Mysticial 是的,您缺少的是当使用T2 类型的左值访问T1 类型的对象时没有别名冲突@ 和T2char,但是当T1char 并且T2 不是char 的有符号/无符号变体时,存在别名冲突。
  • @Mysticial:你搞错了!

标签: c strict-aliasing type-punning


【解决方案1】:

为了解决这个问题,不要双关语和别名!读取类型 T 的唯一“正确”方法是分配类型 T 并在需要时填充其表示:

uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);

简而言之:如果你想要一个整数,你需要做一个整数。没有办法以语言宽恕的方式作弊。

唯一允许的指针转换(通常用于 I/O)是将T 类型的现有变量 的地址视为char*,或者更确切地说, 作为指向大小为 sizeof(T) 的字符数组的第一个元素的指针。

【讨论】:

  • 我不确定sizeof(uint32_t) 是否保证为4,因此您可能需要调整您的memcpy
【解决方案2】:

首先,让我们检查一下为什么会收到别名违规警告。

别名规则简单地说,您只能通过对象自己的类型、其有符号/无符号变体类型或通过字符类型(charsigned charunsigned char )。

C 说违反别名规则会调用未定义的行为(所以不要!)。

在程序的这一行中:

unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));

虽然incoming_buf 数组的元素属于char 类型,但您以unsigned int 的身份访问它们。实际上,表达式*((unsigned int*)dcc->incoming_buf) 中的取消引用运算符的结果是unsigned int 类型。

这违反了别名规则,因为您只能通过charsigned charunsigned char 访问incoming_buf 数组的元素(参见上面的规则摘要!)。

请注意,您的第二个罪魁祸首中存在完全相同的别名问题:

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

您通过unsigned int 访问outgoing_bufchar 元素,因此这是一个别名违规。

建议的解决方案

要解决您的问题,您可以尝试将数组的元素直接定义为您要访问的类型:

unsigned int incoming_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
unsigned int outgoing_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];

(顺便说一下,unsigned int 的宽度是由实现定义的,所以如果你的程序假设 unsigned int 是 32 位,你应该考虑使用 uint32_t)。

通过这种方式,您可以在数组中存储unsigned int 对象,而不会违反别名规则,方法是通过类型char 访问元素,如下所示:

*((char *) outgoing_buf) =  expr_of_type_char;

char_lvalue = *((char *) incoming_buf);

编辑:

我已经完全修改了我的答案,特别是我解释了为什么程序会从编译器获得别名警告。

【讨论】:

    【解决方案3】:

    将指针转换为无符号然后返回指针。

    unsigned int received_size = ntohl (*((unsigned *)((unsigned) dcc->incoming_buf)) );

    【讨论】:

      【解决方案4】:
      union
      {
          const unsigned int * int_val_p;
          const char* buf;
      } xyz;
      
      xyz.buf = dcc->incoming_buf;
      unsigned int received_size = ntohl(*(xyz.int_val_p));
      

      简单的解释 1. c++ 标准规定您应该尝试自己对齐数据,g++ 更加努力地生成有关该主题的警告。 2. 只有在您完全了解架构/系统和代码内部的数据对齐方式时,您才应该尝试它(例如,上面的代码在 Intel 32/64 上是确定的;对齐方式 1;Win/Linux/Bsd/Mac) 3. 使用上面代码的唯一实际原因是避免编译器警告,WHEN 和 IF 你知道你在做什么

      【讨论】:

        【解决方案5】:

        恕我直言,对于这种情况,问题在于 ntohl 和 htonl 以及相关函数 API 的设计。它们不应该被写为带有数字返回的数字参数。 (是的,我理解宏优化点) 它们应该被设计为'n'端作为指向缓冲区的指针。完成此操作后,整个问题就消失了,无论主机是哪种字节序,例程都是准确的。 例如(不尝试优化):

        inline void safe_htonl(unsigned char *netside, unsigned long value) {
            netside[3] = value & 0xFF;
            netside[2] = (value >> 8) & 0xFF;
            netside[1] = (value >> 16) & 0xFF;
            netside[0] = (value >> 24) & 0xFF;
        };
        

        【讨论】:

        • 如果标准包含一组标准的大端和小端“获取”和“填充”例程,即使在使用 @ 987654322@ 不是 8,因此增强了网络代码在此类机器上的可移植性。
        【解决方案6】:

        如果您有理由不允许您更改源对象的类型(就像我的情况一样),并且您绝对确信代码是正确的并且它执行了该 char 数组的预期操作,避免警告您可能会执行以下操作:

        unsigned int* buf = (unsigned int*)dcc->incoming_buf;
        unsigned int received_size = ntohl (*buf);
        

        【讨论】:

        • 抛弃警告是不好的。这段代码没有违反严格的别名,也可能违反对齐限制。
        【解决方案7】:

        我最近将一个项目从 GCC 6 升级到 GCC 9,并开始看到此警告。该项目在 32 位微控制器上,我创建了一个结构来访问 32 位机器寄存器的各个字节:

        struct TCC_WEXCTRL_t
        {
            byte    OTMX;
            byte    DTIEN;
            byte    DTLS;
            byte    DTHS;
        };
        

        然后编码:

        ((TCC_WEXCTRL_t *)&TCC0->WEXCTRL)->DTLS = PwmLoDeadTime;
        

        在新编译器中产生了警告。我发现我可以通过将我的结构与原始类型组合在一起来消除警告:

        union TCC_WEXCTRL_t
        {
            TCC_WEXCTRL_Type std;
            struct  
            {
                byte    OTMX;
                byte    DTIEN;
                byte    DTLS;
                byte    DTHS;
            };    
        };
        

        其中TCC_WEXCTRL_Type 是制造商头文件中提供的WEXCTRL 成员的类型。

        我不确定这是否被认为是完全合规的修复,或者 GCC 是否只是未能捕捉到它。如果这不起作用(或在另一个 GCC 升级中遇到),我将继续使用指针类型的联合,如 Real Name 在此线程中所述。

        【讨论】:

          【解决方案8】:

          C cast 不起作用,但 reinterpret_cast 在类似情况下帮助了我。

          【讨论】:

            猜你喜欢
            • 2016-10-26
            • 2011-11-29
            • 1970-01-01
            • 2014-12-30
            • 1970-01-01
            • 2016-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多