【问题标题】:safety not guaranteed in C buffer calloc or malloc within a function?函数内的 C 缓冲区 calloc 或 malloc 不能保证安全性?
【发布时间】:2013-04-21 19:13:15
【问题描述】:

这是一个关于如何在简单的一点内拥有“腰带和吊带”安全性的问题 C 代码。一个老生常谈的问题是如何确保 可以将数据移动到某个被调用函数内的缓冲区中,而不必担心堆 返回后内存损坏。上面写了一些很棒的东西 关于这个话题的网站,至少对我来说,仍然不清楚我们在哪里得到真正的 总安全。所以我写了以下内容:

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int use_buffer( const char *strin, char **strout, size_t bufsize ) {

    size_t len;
    int result = -1;

    printf ( "in use_buffer() we have address of strout = %p\n", &strout );
    printf ( "            and that contains an address of %p\n", strout );
    printf ( "           which points to a buffer address %p\n", *strout );

    /* check for null data */
    if ( ( strin == NULL ) || ( *strout == NULL ) )
        return result;

    /* check for zero length data */
    if ( strlen(strin) == 0 )
        return result;

    /* ensure we have a non-zero size buffer to write to ?
     * belt and suspenders safety here is not assured. We have
     * no way to know if the calling routine actually did 
     * allocate memory of size bufsize. 
     */
    len = strlen(strin);
    if ( bufsize < len )
        return result;

    strncpy ( *strout, strin, len );

    return len;

}

int main ( int argc, char *argv[] ) {

    char *some_buffer;
    int retval;
    size_t buflen;

    if ( argc < 2 ) { 
        printf ( "usage: %s somestring\n", argv[0] );
        return ( EXIT_FAILURE );
    }

    buflen = (size_t) ( 4 * 4096 );
    some_buffer = calloc( buflen, sizeof( unsigned char) );
    if ( some_buffer == NULL ) { 
        perror ( "Could not calloc a 16Kb byte buffer." );
        return ( EXIT_FAILURE );
    }

    printf ( "main() has a 16Kb buffer ready at address = %p\n",
                                                      &some_buffer );
    retval = use_buffer( argv[1], &some_buffer, buflen );
    if ( retval > 0 )
        printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );

    free ( some_buffer );
    some_buffer = NULL; /* belt and suspenders */

    return ( EXIT_SUCCESS );

}

编译并运行它,我看到了:

$ ./use_buffer "foo of the bar"
main() has a 16Kb buffer ready at address = ffffffff7ffff710
in use_buffer() we have address of strout = ffffffff7ffff630
            and that contains an address of ffffffff7ffff710
           which points to a buffer address 100101440
Maybe we have 14 bytes copied into a buffer.

其中一个地址确实看起来不正确。其中之一就是不一样。

真的,很难知道为什么前三个地址在其他一些内存区域中离得很远,而最后一个看起来可能是一些本地堆内存?

以上方法绝对安全带吊带吗?我的意思是该函数将使用我们知道已预先分配的缓冲区,并且该函数无法将其搞砸。我怀疑该函数是否可以对存储在 strout 中的地址调用 free()。这就像用房间钥匙入住酒店然后放火烧房间一样。站在里面的时候。我想这可以做到..但会很疯狂。

所以这里有两个问题:(1)函数有没有办法验证分配的缓冲区大小?即使方法是触发内存违规。然后(2)将空指针传递给函数然后允许函数根据需要调用/分配缓冲区并传回地址是否安全?

我怀疑(2)已经被打死了,答案是“安全无保障”。 (旁注:顺便说一句,该死的好电影。)

考虑一下这个代码位:

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int bad_buffer( const char *strin, char **strout ) {

    size_t len;
    int result = -1;
    char *local_buffer;

    /* check for null data */
    if ( strin == NULL )
        return result;

    /* check for zero length data */
    len = strlen(strin);
    if ( len == 0 )
        return result;

    /*
     * safety not guaranteed ?
     */
    local_buffer = calloc( len+1, sizeof( unsigned char) );

    printf ( "  in bad_buffer() we have local_buffer at = %p\n",
                                                    &local_buffer );

    strncpy ( local_buffer, strin, len );
    *strout = local_buffer;

    return len;

}

int main ( int argc, char *argv[] ) {

    char *some_buffer;
    int retval;
    size_t buflen;

    if ( argc < 2 ) { 
        printf ( "usage: %s somestring\n", argv[0] );
        return ( EXIT_FAILURE );
    }

    retval = bad_buffer( argv[1], &some_buffer );
    printf ( "in main() we now have some_buffer at addr %p\n", some_buffer );

    if ( retval > 0 )
        printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );

    printf ( "main() says the buffer contains \"%s\"\n", some_buffer );

    free ( some_buffer ); /* really ?  main() did not allocate this !? */
    some_buffer = NULL;

    return ( EXIT_SUCCESS );

}

当我编译并运行时,我看到了:

$ ./bad_buffer foobar
  in bad_buffer() we have local_buffer at = ffffffff7ffff6b0
in main() we now have some_buffer at addr 100101330
Maybe we have 6 bytes copied into a buffer.
main() says the buffer contains "foobar"

这里似乎有些诡异。该函数执行了 calloc,然后将缓冲区的地址填充到 strout 内的 address 中。所以 strout 是一个指向指针的指针,所以我很好。让我害怕的是,函数分配的内存在完成后没有权利或理由被认为是安全的,我们又回到了 main()。

所以问题号 (2) 代表“允许函数调用/分配所需的缓冲区是否有任何安全性?”

【问题讨论】:

  • @Cratylus:它在use_buffer 的顶部。 OP,不能保证您将始终收到动态分配的缓冲区的地址,该缓冲区靠近先前分配的缓冲区。你有一个指向指针的指针。 指针本身的地址通常与它所指的地址有很大不同
  • 如果我这样做char *p = malloc(size)p 将分配自动存储持续时间,实际上,&amp;p 将是堆栈中某处的地址。 p 是动态分配缓冲区的地址(实际上是在堆上)。这就是它们不同的原因。但是,不需要解释;您永远不会,也不会一次,通过任何形式的语言保证动态分配的缓冲区的地址应该在彼此之间的一小段距离内。
  • 你的前提是错误的。同样,指针本身的地址很可能不会位于作为其的地址附近的任何位置。它们是两个完全不同的东西。如果我是编译器,我会说“哦,我需要在这个函数中分配一个指针。酷,我会把它放在堆栈上。哦,它是用malloc的返回值初始化的。malloc会在运行时找到一个空位并返回一个地址。这将是指针的”。 mallocpointer 本身 的存储位置无关,只是用 value 对其进行初始化。
  • @paullanken:是的。如果动态分配的缓冲区是限定范围的,那么就不会有内存泄漏之类的事情(当然,C 中的编程也会变得更加困难。如何在不到处复制大副本的情况下共享内存?)当你通过指向或来自函数的指针 复制(C 中的所有内容都按值传递)。指针的值,即它所指的,当然是不变的,但是指针本身就是一个副本。
  • 我很少需要将一个字符串复制到另一个字符串的中间。如果我愿意,我会使用 memcpy()。请记住:strncpy(dst,src,len) 总是触及 len 字节。如果src 的实际长度恰好小于这个值,它将用 NUL 填充其余部分。很少需要 Strncpy()。

标签: c memory


【解决方案1】:

其中一个地址确实看起来不正确。其中之一是 只是不一样。

真的,很难知道为什么前三个地址相差甚远 在其他一些内存区域疯狂,而最后一个看起来是 也许是一些本地堆内存?

some_buffer(或其别名strout)是存储在main堆栈中的局部变量,并指向中的地址。所以它们是不同内存区域的地址

【讨论】:

    【解决方案2】:
    $ ./use_buffer "foo of the bar"
    main() has a 16Kb buffer ready at address = ffffffff7ffff710
    in use_buffer() we have address of strout = ffffffff7ffff630
                and that contains an address of ffffffff7ffff710
               which points to a buffer address 100101440
    Maybe we have 14 bytes copied into a buffer.
    

    这里有 3 个第一个值在堆栈中。查看您的代码,中间的实际上是该函数的本地参数的地址,而第一个和第三个是main() 中的局部变量的地址。注意堆栈是如何向下增长的,因此它位于高地址,并且被调用的函数参数低于调用函数的变量。

    第四个值是一种特殊情况,因为它是argv 字符串的地址。这些字符串要么是全局变量(在它们自己的程序地址空间部分中),要么它们甚至可以位于不靠近其他任何东西的操作系统特定特殊地址。


    $ ./bad_buffer foobar
      in bad_buffer() we have local_buffer at = ffffffff7ffff6b0
    in main() we now have some_buffer at addr 100101330
    Maybe we have 6 bytes copied into a buffer.
    main() says the buffer contains "foobar"
    

    在这里,第一个地址在堆栈中,函数中局部变量的地址。第二个地址是堆中的内存,main() 中指针的


    是的,一目了然,您的代码是安全的。您似乎将指针值与指针变量的地址混淆了。考虑一下:

    char *p1 = malloc(10);
    char *p2 = p1;
    printf("%p %p %p %p\n", &p1, &p2, p1, p2);
    

    上面会打印类似的东西

    ffffffff7ffff710 ffffffff7ffff702 100101440 100101440
    

    首先是变量p1的地址,它是这里和堆栈中的局部变量。第二个是p2 的地址,也是局部变量和堆栈。然后最后两个地址作为malloc调用的返回值,分配给p1并复制到p2。该分配的块将一直保留到您free 它,无论您传递地址多少次,或者即使您丢失了地址(在这种情况下您有内存泄漏)。当您free 它时,任何仍指向该区域的指针都将成为悬空指针,不应取消引用。

    【讨论】:

    • 太棒了。我认为问题的真正症结在于内存区域实际上是安全的,并且一直保持这种状态,直到我在指向它的指针上调用 free() 为止。 calloc 是在 main() 还是在某个函数中完成并不重要。我说的对吗?
    • 在答案中添加了另外两个部分,其中一个专门涉及堆分配。
    • 很棒的东西。我得到它。我错误地担心分配的内存区域在某种程度上是临时的,并且仅在执行 calloc 的函数的范围内。错误的。现在我看到并接受了教育,并感谢这个网站以及像你和 Ed S 这样的伟大人物。
    • 谢谢。是的,返回局部变量的地址是没有经验的 C 程序员常犯的一个错误(尤其是 C++,返回对局部变量的引用存在额外的危险),因此确实需要注意。
    猜你喜欢
    • 2014-10-16
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    相关资源
    最近更新 更多