【问题标题】:C: malloc of string showing unexpected behaviour with double pointer and function callsC: 字符串的 malloc 显示双指针和函数调用的意外行为
【发布时间】:2020-04-06 06:08:45
【问题描述】:

这是一个查询,用于了解以下代码即使出现错误也能正常工作。

据我所知,如果我想重新分配/重新分配传递给函数的指针,则该指针需要作为双指针传递。错误地,我传递了一个指针,程序仍在运行。我猜它必须与指针作为字符串有关。

程序:

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

void func2( char **x){

    printf("befor func2 x = %u; *x = %u; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
    free(*x);
    *x = (char *)malloc(20);
    strcpy(*x, "zyxwvutsrqponmlkjih");

    printf("\n\nafter func2 x = %u; *x = %u; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
}

void func1( char *x){

    printf("befor func1 &x = %u; x = %u; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
    func2(&x);
    printf("after func1 &x = %u; x = %u; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
}

int main(){

    char *x; 
    x = (char *)malloc(10);
    strcpy(x, "abcdefghi");
    printf("befor  main &x = %u; x = %u; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    func1(x);
    printf("after  main &x = %u; x = %u; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    free(x);
    return 1;
}

输出:

befor  main &x = 489275896; x = 20414480; x = abcdefghi; x_size = 9
befor func1 &x = 489275864; x = 20414480; *x = abcdefghi; x_size = 9 
befor func2 x = 489275864; *x = 20414480; **x = abcdefghi; x_size = 9


after func2 x = 489275864; *x = 20414480; **x = zyxwvutsrqponmlkjih; x_size = 19
after func1 &x = 489275864; x = 20414480; *x = zyxwvutsrqponmlkjih; x_size = 19 
after  main &x = 489275896; x = 20414480; x = zyxwvutsrqponmlkjih; x_size = 19

直到func1,我都能理解输出。但是在func2 中修改后,大小和值如何返回到main?我没有将x 作为双指针从main 传递到func1。但不知何故,它仍然有效。
是因为是char *吗?

编辑 1:

在 cmets 中建议编辑后:

程序:

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

void func2( char **x){

    printf("befor func2 x = %p; *x = %p; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
    free(*x);
    *x = (char *)malloc(20);
    strcpy(*x, "zyxwvutsrqponmlkjih");

    printf("\n\nafter func2 x = %p; *x = %p; **x = %s; x_size = %u\n", x, *x, *x, strlen(*x));
}

void func1( char *x){

    printf("befor func1 &x = %p; x = %p; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
    func2(&x);
    printf("after func1 &x = %p; x = %p; *x = %s; x_size = %u \n", &x, x, x, strlen(x));
}

int main(){

    char *x, *y, *z; 
    x = (char *)malloc(10);
    z = (char *)malloc(100);
    y = (char *)malloc(100);
    strcpy(x, "abcdefghi");
    printf("befor  main &x = %p; x = %p; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    func1(x);
    printf("after  main &x = %p; x = %p; x = %s; x_size = %u\n", &x, x, x, strlen(x));
    free(x);
    free(y);
    free(z);
    return 1;
}

输出:

befor  main &x = 0x7fff78cb09c8; x = 0x1c7a010; x = abcdefghi; x_size = 9
befor func1 &x = 0x7fff78cb09a8; x = 0x1c7a010; *x = abcdefghi; x_size = 9 
befor func2 x = 0x7fff78cb09a8; *x = 0x1c7a010; **x = abcdefghi; x_size = 9


after func2 x = 0x7fff78cb09a8; *x = 0x1c7a010; **x = zyxwvutsrqponmlkjih; x_size = 19
after func1 &x = 0x7fff78cb09a8; x = 0x1c7a010; *x = zyxwvutsrqponmlkjih; x_size = 19 
after  main &x = 0x7fff78cb09c8; x = 0x1c7a010; x = zyxwvutsrqponmlkjih; x_size = 19

引入多个 malloc 后程序仍然有效。

【问题讨论】:

  • 在不相关的注释中,打印指针的正确printf 格式(更具体地说是void *,需要转换)是%p。格式说明符和参数类型不匹配(例如使用unsigned int 格式%u 打印指针)会导致未定义行为
  • 这可能是纯粹的巧合和运气,再加上您的程序具有非常简单的内存布局这一事实,让它发生。由于您总是在再次分配之前释放,因此 malloc 能够一遍又一遍地返回相同的指针。为了验证这一点,您可以通过在声明 x 之后立即添加 char *y = malloc(10); 来“破坏”程序。然后,func2 的 malloc 将无法为其更大的缓冲区使用相同的 10 字节长的存储空间,并且应该返回其他地方……尽管它仍然依赖于实现。 (不是一个答案,因为没有经过验证也没有写好。)
  • @Someprogrammerdude 我忘记了 %p。谢谢提醒。我最初使用的是%02x。但这只有1个字符差异并且令人困惑。所以我选择了 %u。
  • @Alceste_ 我尝试了您的建议并根据调查结果更新了问题。
  • Tbh 我仍然倾向于坚持我的解释(一些程序员老兄的回答很好地增强了这一点。也许你的 malloc 实现在缓冲区之间有一个超过 100 字节的默认空间,这将简化 realloc 优化. 你可以通过为你的程序设置一个较小的整体内存来打破它,或者更容易地,使用兆字节缓冲区作为字符串。(如果需要的话,仍然先使用少数几个)

标签: c string pointers malloc double-pointer


【解决方案1】:

你所拥有的是未定义的行为

下面是程序的重要部分(重命名的变量可以在函数中区分它们):

void func2(char **x)
{
    free(*x);
    *x = malloc(SOME_OTHER_SIZE);
}

void func1(char *y)
{
    func2(&y);
}

int main(void)
{
    char *z = malloc(SOME_SIZE);
    func1(z);
}

main 函数中分配一些内存,并让z 指向它。

然后你调用func1传递指针z按值,这意味着指针被复制到func1参数变量y。现在有两个指针指向同一个内存:main 函数中的zfunc1 函数中的y

然后func1 调用func2,但它模拟按引用传递,传递的不是y 中值的副本,而是指向变量y 本身的指针。当func2释放*x指向的内存时,它会使*xyz指针失效。然后它重新分配*x 以指向一些新内存。这将更改y 指向的位置,但不会更改z,这仍然无效。

func1 返回指针z 不再有效时,任何取消引用它的尝试都会导致上述未定义行为


从图形上看是这样的:

  1. main 函数分配内存并使z 指向它:

    +---+     +-----------+
    | z | --> | Memory... |
    +---+     +-----------+
    
  2. 调用函数func1,传递z的副本:

    +---+
    | z | -\
    +---+   \    +-----------+
             >-> | Memory... |
    +---+   /    +-----------+
    | y | -/
    +---+
    
  3. 函数func2被调用,传递一个指向y的指针:

              +---+
              | z | -\
              +---+   \    +-----------+
                       >-> | Memory... |
    +---+     +---+   /    +-----------+
    | x | --> | y | -/
    +---+     +---+
    
  4. 函数func2free是*x指向的内存:

              +---+
              | z | -\
              +---+   \    
                       >-> ???
    +---+     +---+   /    
    | x | --> | y | -/
    +---+     +---+
    
  5. 函数func2 分配新内存并使*x(因此y)指向它:

              +---+
              | z | --> ???
              +---+       
    
    +---+     +---+     +---------------+
    | x | --> | y | --> | New memory... |
    +---+     +---+     +---------------+
    

从上面应该很容易看出为什么func2 中的free(*x) 也会使main 函数中的z 无效。

现在有趣的部分,这就是为什么 zmain 函数中指向的内存似乎发生了变化:这似乎是您系统中内存分配器的一个怪癖,它映射新的分配到与旧分配相同的位置。重要的是z仍然无效。

【讨论】:

    【解决方案2】:

    您似乎认为使用char *x(单个指针)作为func1 的参数是错误的。不过,我觉得完全没问题。 func1 期望一个指向 char 作为参数的指针,或者基本上是一个内存地址,当取消引用时会给出一个 char 或一堆 char。当你在 main 中写 func1(x); 时,你传递的是 x,一堆字符的地址,这正是 func1 所期望的参数类型。

    为什么 x 是一堆字符的地址? 在这种情况下,指针x 正在存储一个数组(字符)的地址。现在,您可能知道,如果您只写一个数组的名称,就会得到数组的基地址。见以下代码:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main () {
        int arr[5] = {1, 2, 3, 4, 5};
        printf ("%d\n", arr);       // address of the first element (1) of the array
                                    // or, the base address of the array
        printf ("%d\n", &arr[0]);   // same as above
        printf ("%d\n\n", *arr);      // gives the first element of the array
    
        int *x = malloc (5*sizeof (int));
        *x = 1;
        *(x+1) = 2;
        *(x+2) = 3;
        *(x+3) = 4;
        *(x+4) = 5;
        printf ("%d\n", x);        // address of the first element (1) of the array
                                   // or, the base address of the array
        printf ("%d\n", &*(x+0));  // same as above
        printf ("%d\n\n", *x);     // gives the first element of the array
        return 0;
    }
    

    输出如下:

    6356712
    6356712
    1
    
    9768168
    9768168
    1
    

    现在,为什么我们可以修改 x 指向的字符数组中的值?因为我们已经传递了该数组的基地址。我们可以取消引用该地址以获取数组并使用它做任何我们想做的事情。

    【讨论】:

    • 我认为这个问题的答案没有任何相关性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 2018-08-16
    • 2021-09-02
    • 1970-01-01
    • 1970-01-01
    • 2018-02-10
    • 2014-04-12
    相关资源
    最近更新 更多