C语言 修复了取消引用类型双关指针将破坏严格锯齿的问题



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

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

而罪魁祸首是:

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];

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

有很多使用联合的建议 - 在这种情况下,什么可能是合适的联合?

首先,让我们来看看为什么会出现混叠冲突警告。

别名规则只是说你只能通过它自己的类型、有符号/无符号的变体类型或通过字符类型(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元素,因此这是混叠冲突。

建议的解决方案

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

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

编辑:

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

要解决此问题,请不要双关语和别名!读取类型T的唯一"正确"方法是分配类型T并在需要时填充其表示形式:

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

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

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

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. 只有当您完全了解架构/系统和代码内部的数据对齐时,您才应该尝试这样做(例如上面的代码在英特尔 32/64 上是确定的事情; 对齐 1;Win/Linux/Bsd/Mac(3.使用上述代码的唯一实际原因是避免编译器警告,WHEN和IF 你知道你在做什么

恕我直言,在这种情况下,问题在于 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;
};

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

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

我最近将一个项目从 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 在此线程上描述的那样。

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

如果您确定自己知道自己在做什么,请执行以下操作:

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

或只是:

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

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

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

最新更新