首页 文章

修复取消引用类型惩罚指针将破坏严格别名

提问于
浏览
19

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

warning: dereferencing type-punned pointer will break strict-aliasing rules [-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];

这似乎与我一直在研究的那个警告的其他例子略有不同 . 我宁愿修复问题而不是禁用严格别名检查 .

有很多建议要使用联合 - 这个案例可能是一个合适的联合体?

5 回答

  • 30

    首先,让我们来看看你为什么会得到别名违规警告 .

    Aliasing rules 只是说您只能通过自己的类型,签名/无符号变体类型或字符类型( 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 类型 .

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

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

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

    您通过 unsigned int 访问 outgoing_bufchar 元素,因此它是一个锯齿违规 .

    Proposed solution

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

    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 ) .

    这样,您可以通过类型 char 访问元素,从而在不违反别名规则的情况下将 unsigned int 对象存储在数组中,如下所示:

    *((char *) outgoing_buf) =  expr_of_type_char;
    

    要么

    char_lvalue = *((char *) incoming_buf);
    

    EDIT:

    我完全重写了我的答案,特别是我解释了为什么程序从编译器获得别名警告 .

  • -2

    将指针转换为unsigned,然后返回指针 .

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

  • 0

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

    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;
    };
    
  • 17
    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你知道你在做什么

  • 3

    要解决这个问题,请不要使用双关语和别名!读取类型 T 的唯一"correct"方法是分配类型 T 并在需要时填充其表示:

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

    简而言之:如果你想要一个整数,你需要做一个整数 . 没有办法以语言宽容的方式欺骗它 .

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

相关问题