c-GCC警告数组越界,误报



我有一个简单的函数

#define IP_AS_V6(storage, f) ((struct sockaddr_in6 *)&(storage))->sin6_##f
static inline int ip_check_equal_v6
(const struct sockaddr_storage *a, const struct sockaddr_storage *b)
{ return ((uint64_t *)IP_AS_V6(a, addr).s6_addr)[0] == ((uint64_t *)IP_AS_V6(b, addr).s6_addr)[0] &&
((uint64_t *)IP_AS_V6(a, addr).s6_addr)[1] == ((uint64_t *)IP_AS_V6(b, addr).s6_addr)[1]; }

在切换到GCC-12后,由于,它不再编译

error: array subscript 2 is outside array bounds of 'const struct sockaddr_storage[0]' [-Werror=array-bounds]
5 |              ((uint64_t *)IP_AS_V6(a, addr).s6_addr)[1] == ((uint64_t *)IP_AS_V6(b, addr).s6_addr)[1]; }
|                                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~

IPv6地址是128位,因此当将其视为64位int数组时,索引1应该非常好。这段代码是否由于其他原因而非法/未定义,或者这确实是gcc-12中的一个错误?

让我们看看您的宏实际在做什么。鉴于

const struct sockaddr_storage *a;

表达式CCD_ 1扩展为…

(uint64_t *)((struct sockaddr_in6 *)&(a))->sin6_addr.s6_addr)[0]

首先,您的间接级别搞砸了,至少如果a实际上指向struct sockaddr_in6&(a)是指向结构指针的指针,它对您的目的毫无用处。你的意思大概只是(a),而不是它的地址。这可能是编译器抱怨的根源。

其次,(struct sockaddr_in6 *)(a))->sin6_addr.s6_addr的类型为unsigned char[16]。它会自动转换为unsigned char *,您可以安全地将其转换为类型uint64_t *,但也可能无法。如果(uint64_t *)IP_AS_V6(a, addr).s6_addr)[0]0的结果指针没有正确对齐,那么会产生未定义的行为,我不清楚是否有什么可以确保对齐成功。

第三,即使对齐成功,通过类型为uint64_t的左值访问unsigned char[]的部分也违反了严格的混叠规则,从而产生未定义的行为。

该函数还丢弃了constness,这是一种糟糕的形式,应该会引发警告,即使它没有尝试修改任何一个对象。

我想我会这样写函数:

static inline int ip_check_equal_v6(const struct sockaddr_storage *a,
const struct sockaddr_storage *b) {
const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *) a;
const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *) b;
return !memcmp(a6->sin6_addr.s6_addr, a6->sin6_addr.s6_addr,
sizeof(a6->sin6_addr.s6_addr));
}

如果第一个地址中的地址字节与第二个地址相同(两者都被解释为IPV6地址(,则返回1;如果其中任何一个地址不同,则返回0,这似乎是您的版本想要做的。请注意,比原来的读起来容易得多,这使得它更容易维护,甚至更容易用眼睛评估。您的编译器甚至可能比原来的编译器更好地优化它,尽管我对此没有任何承诺。

最新更新