【问题标题】:malloc() and memset() behaviormalloc() 和 memset() 行为
【发布时间】:2019-06-13 05:18:47
【问题描述】:

我编写了一些代码来查看malloc()memset() 的行为,我发现了一个我不知道发生了什么的情况。

我使用malloc() 为字符数组分配了 15 字节的内存,并且我 想看看如果我错误地使用 memset() 在我创建的指针中设置 100 字节的内存会发生什么。我希望看到 memset() 设置了 15 个字节(并且可能会丢弃一些其他内存)。我在运行程序时看到的是它为我编码的字符设置了 26 字节的内存。

知道为什么要为我创建的指针分配 26 个字节吗?我正在使用 gcc 和 glibc 进行编译。代码如下:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define ARRLEN 14

int main(void) {

    /* + 1 for the null terminator */
    char *charptr = malloc((sizeof(*charptr) * ARRLEN) + 1);
    if (!charptr)
        exit(EXIT_FAILURE);

    memset(charptr, '\0', (sizeof(*charptr) * ARRLEN) + 1);

    /* here's the intentionally incorrect call to memset() */
    memset(charptr, 'a', 100);

    printf("sizeof(char)   ------  %ld\n", sizeof(char));
    printf("sizeof(charptr)   ---  %ld\n", sizeof(charptr));
    printf("sizeof(*charptr)  ---  %ld\n", sizeof(*charptr));
    printf("sizeof(&charptr)  ---  %ld\n", sizeof(&charptr));
    printf("strlen(charptr)   ---  %ld\n", strlen(charptr));
    printf("charptr string   ----  >>%s<<\n", charptr);

    free(charptr);

    return 0;
}

这是我得到的输出:

sizeof(char)   ------  1
sizeof(charptr)   ---  8
sizeof(*charptr)  ---  1
sizeof(&charptr)  ---  8
strlen(charptr)   ---  26
charptr string   ----  >>aaaaaaaaaaaaaaaaaaaaaaaa<<

【问题讨论】:

  • 你正在导致未定义的行为,所以任何事情都可能发生。
  • 知道为什么要为我创建的指针分配 26 个字节吗? 未定义的行为是未定义的。可能有什么东西将新数据写入了你用狂野 memset() 破坏的内存。
  • 较早的printf() 调用之一可能将零写入了它自己的堆变量之一,而这些变量恰好位于您分配的字符串之后的 11 个字节处。
  • 有趣的是,我看到的是一样的,但只是禁用了优化;打开优化会在编译时(令人印象深刻的工作gcc!)警告潜在的缓冲区溢出,并在运行时令人满意地崩溃。
  • #include &lt;unistd.h&gt; 是不必要的。您不使用该标题中的任何内容。

标签: c gcc malloc glibc memset


【解决方案1】:

首先,这是未定义的行为,所以任何事情都可能发生;正如评论中所说,在我的机器上,我在禁用优化的情况下得到了完全相同的行为,但是打开优化我收到一个关于潜在缓冲区溢出的警告在编译时(令人印象深刻的工作 gcc!)和运行时大崩溃。更好的是,如果我在调用printf 之前使用puts 打印它,我会使用不同数量的a 打印它。

不过,我有幸遇到与您完全相同相同的行为,所以让我们来调查一下。我编译你的程序没有优化和调试信息

[matteo@teokubuntu ~/scratch]$ gcc -g memset_test.c 

然后我启动了调试器并在第一个printf 上添加了一个断点,就在memset 之后。

Reading symbols from a.out...done.
(gdb) break 20
Breakpoint 1 at 0x87e: file memset_test.c, line 20.
(gdb) r
Starting program: /home/matteo/scratch/a.out 

Breakpoint 1, main () at memset_test.c:20
20          printf("sizeof(char)   ------  %ld\n", sizeof(char));

现在我们可以在charptr指向的第26个内存位置设置硬件写断点

(gdb) p charptr
$1 = 0x555555756260 'a' <repeats 100 times>
(gdb) watch charptr[26]
Hardware watchpoint 2: charptr[26]

……等等……

(gdb) c
Continuing.

Hardware watchpoint 2: charptr[26]

Old value = 97 'a'
New value = 0 '\000'
_int_malloc (av=av@entry=0x7ffff7dcfc40 <main_arena>, bytes=bytes@entry=1024) at malloc.c:4100
4100    malloc.c: File o directory non esistente.
(gdb) bt
#0  _int_malloc (av=av@entry=0x7ffff7dcfc40 <main_arena>, bytes=bytes@entry=1024) at malloc.c:4100
#1  0x00007ffff7a7b0fc in __GI___libc_malloc (bytes=1024) at malloc.c:3057
#2  0x00007ffff7a6218c in __GI__IO_file_doallocate (fp=0x7ffff7dd0760 <_IO_2_1_stdout_>) at filedoalloc.c:101
#3  0x00007ffff7a72379 in __GI__IO_doallocbuf (fp=fp@entry=0x7ffff7dd0760 <_IO_2_1_stdout_>) at genops.c:365
#4  0x00007ffff7a71498 in _IO_new_file_overflow (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, ch=-1) at fileops.c:759
#5  0x00007ffff7a6f9ed in _IO_new_file_xsputn (f=0x7ffff7dd0760 <_IO_2_1_stdout_>, data=<optimized out>, n=23)
    at fileops.c:1266
#6  0x00007ffff7a3f534 in _IO_vfprintf_internal (s=0x7ffff7dd0760 <_IO_2_1_stdout_>, 
    format=0x5555555549c8 "sizeof(char)   ------  %ld\n", ap=ap@entry=0x7fffffffe330) at vfprintf.c:1328
#7  0x00007ffff7a48f26 in __printf (format=<optimized out>) at printf.c:33
#8  0x0000555555554894 in main () at memset_test.c:20
(gdb) 

所以,这只是printf 调用的malloc 代码(或多或少间接),在紧邻它提供给您的内存块上执行其操作(可能将其标记为已使用)。

长话短说:你拿走了不属于你的记忆,现在它的合法所有者在第一次需要时就对其进行了修改;没有什么特别奇怪或有趣的。

【讨论】:

  • gdb 演练非常有帮助。我从没想过为“charptr[26]”设置观察点,只为“charptr”设置观察点。
  • @Schmecky:很高兴它有帮助,请随时投票 ;-) ;请注意,为直接charptr 设置观察点将观察指针变量值的变化,而不是它指向的内存。
【解决方案2】:

我使用 malloc() 为字符数组分配 15 个字节的内存, 我想看看如果我错误地使用 memset() 会发生什么 在我创建的指针中设置 100 字节的内存。

就语言标准而言,发生的是未定义的行为,并且在特定情况下实际发生的任何事情都无法从源代码中预测,并且可能在不同的 C 实现之间不一致或甚至同一程序的不同运行。

我希望看到 memset() 设置了 15 个字节(并且可能会丢弃一些其他内存)。

这是一个看似合理的结果,但你有任何特定的期望是危险的。不要假设你可以预测 UB 会采取什么表现形式,甚至不能基于过去的经验。而且由于您不应该这样做,并且无法从中学到任何有用的东西,因此不值得尝试 UB。

我在运行程序时看到的是它设置了 26 个字节 我编码的字符的内存。

知道为什么要为我创建的指针分配 26 个字节吗?

谁说你的实验证明了这一点?不仅memset(),最后的printf()也展出了UB。除了输出是什么之外,输出什么也不告诉你。那个时候。

现在一般来说,malloc 可能会保留比您请求更大的块。许多实现在内部以大于 1 个字节的块(例如 16 或 32 个字节)管理内存。但这与程序行为的定义没有任何关系,也不会影响您的输出。

【讨论】:

  • 那么,最后一个 printf() 是否表现出未定义的行为,因为字符数组的末尾没有空终止符?
  • 是的,@Schmecky。或者更具体地说,因为在对象的分配大小中没有空终止符 - 正如 malloc() 指定的那样 - printf 将尝试越界访问。
  • 您对未定义的行为进行试验也是对的。我基本上就像一只拿着火柴的猴子一样,应该专注于标准。好建议。
猜你喜欢
  • 2013-11-17
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 2013-11-14
  • 2013-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多