警告:'memset'将始终溢出[-Wfortify-source]

aelbi1ox  于 2023-05-06  发布在  其他
关注(0)|答案(3)|浏览(246)

尝试在struct sin6_addr中操纵IPv6地址,例如清洗它:

struct sockaddr a;
memset(&(((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr), 0, 16);
printf("SIZEOF: %lu\n", sizeof((((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr)));

面对警告:

1.c:34:2: warning: 'memset' will always overflow; destination buffer has size 8, but size argument is 16 [-Wfortify-source]
        memset(&(((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr), 0, 16);

同时,printf("SIZEOF...")返回16个字节,那么应该有足够的空间了吧?
如果我这样做:

struct sockaddr a;
struct in6_addr aa;
aa = ((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr;
memset(&aa, 0, 16);

未显示任何警告。我做错了什么?
UPD。修复了printf()中的a

luaexgnf

luaexgnf1#

Attie已经给出了完整的答案,但这给出了存储套接字地址的替代方案:

struct sockaddr_storage

socket(7)的手册页说明如下:
sockets API提供数据类型struct sockaddr_storage。
这种类型适合容纳所有
支持特定于域的套接字地址结构;它很大
足够,并正确对齐。
sockaddr_storage结构在必须
以通用方式处理套接字地址(例如,必须处理IPv4和IPv6套接字地址的程序)。
因此,与此相反:

struct sockaddr a;

你应该使用这个:

struct sockaddr_storage a;

用于为大多数不同的地址类型获得足够的空间。

ecfdbz9o

ecfdbz9o2#

不幸的是,这些结构不是这样工作的...你会发现struct sockaddr是一个“base”定义,它实际上只包括socket家族字段和一些最小的空间保留-然后通过类型转换,你可以访问另一个结构的字段。当你使用IPv6时,这意味着你将有一个更长的地址,并且需要相应地分配存储。

  • struct sockaddr
  • struct sockaddr_in
  • struct sockaddr_in6

由于各种寻址方案使用不同的信息(例如:地址长度),您需要确保为您尝试使用的内容分配足够的存储空间。
在您的情况下,以下方法将起作用:

struct sockaddr_in6 a;
memset(&(a->sin6_addr.s6_addr), 0, sizeof(a->sin6_addr.s6_addr));
printf("SIZEOF: %lu\n", sizeof(a->sin6_addr.s6_addr));

/* don't be afraid to clear the whole structure before filling it in too! */
memset(&a, 0, sizeof(a));

当您随后使用它来连接/绑定时,您可以将其类型转换到那里。当把结构传递给内核时,你几乎可以把它看作是传递一个无类型的指针。。内核知道你使用的是IPv6(因为你在调用socket()时和在地址族字段中这样说),因此知道要使用哪种类型。这里的类型转换只是让编译器高兴-您仍然共享完整的IPv6信息,以及该结构的大小。

int fd, ret;

fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
ret = connect(fd, (struct sockaddr *)&a, sizeof(a));

把结构看作是一个你可以放置在内存上的模板--它只是一个如何读取内存中保存的数据的指示器……通过将内存区域转换为另一种类型,根本不会导致底层的“thing”改变形状。
如果在堆(而不是堆栈)上分配地址,那么当调用*alloc()时,需要为正在使用的地址给予正确的大小。

struct sockaddr_in6 *a;

a = malloc(sizeof *a);
a = malloc(sizeof (struct sockaddr_in6)); /* this works too... */

a = malloc(sizeof (struct sockaddr)); /* this is BROKEN */
z18hc3ub

z18hc3ub3#

关于这部分的问题:
如果我这样做:

struct sockaddr a;
struct in6_addr aa;
aa = ((struct sockaddr_in6 *)&a)->sin6_addr.s6_addr;
memset(&aa, 0, 16);

未显示任何警告。我做错了什么?
这段代码有几个问题。
首先,这个演员表

(struct sockaddr_in6 *)&a

是完全错误的a不是struct sockaddr_in6,而是struct sockaddr。有了这个强制转换,你基本上对编译器撒谎了。将该内存视为它不是的东西是strict aliasing violation,这会调用未定义的行为。(例外情况是,它始终允许访问任何作为[[un]signed] char数据的对象。
然后,通过访问不是struct sockaddr_in6的某个对象的sin6_addr.s6_addr,您可能会访问实际struct sockaddr a对象之外的内存如果struct sockaddr_in6struct sockaddr有更严格的对齐要求,那么可能会出现任何对齐问题。违反其中任何一项都将也会调用未定义的行为。并且注意,仅仅创建一个未对齐的指针就足以调用未定义的行为--取消引用这样的指针对于调用未定义的行为是不必要的。
既然现在知道struct sockaddr_in6struct sockaddr大,那么就知道访问了struct sockaddr a对象之外的内存。这是未定义的行为。
你还在阅读未初始化的数据-和that invokes undefined behavior too
该代码以多达四种不同的方式调用未定义的行为:
1.严格混叠违规
1.在对象边界之外进行访问
1.对齐违例
1.读取未初始化数据
任何类型的未定义行为的结果之一是“看起来像预期的那样工作”。
根据您使用的编译器,增加警告级别和/或其他选项可能会生成一些警告和/或错误。GCC的-Wall -pedantic * 可能 * 在这里有所帮助。
是的,POSIX在使用严格的别名时会有一点松散,当您必须将struct sockaddr_in6转换为struct sockaddr才能实际使用该对象时。但是POSIX标准does impose requirements要求符合POSIX标准的实现必须允许这样做(C标准未定义的行为的一个特征是允许实现实际定义行为):
当指向sockaddr_storage结构的指针被转换为指向sockaddr结构的指针时,sockaddr_storage结构的ss_family字段将Map到sockaddr结构的sa_family字段。当指向sockaddr_storage结构的指针被转换为指向协议特定地址结构的指针时,ss_family字段应Map到该结构的类型为sa_family_t并且标识协议的地址族的字段上。
(In我的意见是,POSIX的要求并不足以涵盖所有预期工作的sockaddr_*用法/转换/强制转换...)

相关问题