C: mallo c строки, показывающей неожиданное поведение с двойным указателем и вызовами функций - PullRequest
0 голосов
/ 06 апреля 2020

Это запрос, чтобы понять, как приведенный ниже код работает нормально даже с ошибкой.

Насколько я знаю, если я хочу перераспределить / переназначить указатель, переданный функции, этот указатель должен быть передан как двойной указатель. По ошибке я передал один указатель, и программа все еще работает. Я предполагаю, что это должно что-то делать с указателем, являющимся строкой.

Программа:

#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;
}

OutPut:

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. Но как размер и значения возвращаются в main после изменения в func2? Я не передал x как двойной указатель от main до func1. Но так или иначе это все еще работает. Это потому что это char *?

Правка 1:

После предложенных правок в комментариях:

Программа:

#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.

Ответы [ 2 ]

3 голосов
/ 06 апреля 2020

Что у вас есть неопределенное поведение .

Ниже приведены важные части вашей программы (с переименованными переменными, чтобы иметь возможность различаться между ними в функциях):

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. Теперь у вас есть два указателя, указывающих на одну и ту же память: z в функции main и y в функции func1.

Затем func1 вызывает func2, но это эмулирует передачу по ссылке , передавая не копию значения в y, а указатель на саму переменную y. Когда func2 освобождает память, на которую указывает *x, это делает недействительными указатели *x, y и z. Затем он переназначает *x, чтобы указать на новую память. Это изменится, когда y указывает, но не z, что все равно будет недействительным.

Когда func1 вернет указатель z больше не действителен, любая попытка разыменования приведет к неопределенное поведение .


Несколько графически это можно увидеть так:

  1. Функция main выделяет память и делает z указать на это:

    +---+     +-----------+
    | z | --> | Memory... |
    +---+     +-----------+
    
  2. Вызвана функция func1 с передачей копии z:

    +---+
    | z | -\
    +---+   \    +-----------+
             >-> | Memory... |
    +---+   /    +-----------+
    | y | -/
    +---+
    
  3. Функция func2 вызывается, передавая указатель на y:

              +---+
              | z | -\
              +---+   \    +-----------+
                       >-> | Memory... |
    +---+     +---+   /    +-----------+
    | x | --> | y | -/
    +---+     +---+
    
  4. Функция func2 освобождает память, на которую указывает *x:

              +---+
              | z | -\
              +---+   \    
                       >-> ???
    +---+     +---+   /    
    | x | --> | y | -/
    +---+     +---+
    
  5. Функция func2 выделяет новую память и заставляет *x (и, следовательно, y) указывать на нее:

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

Из всего вышесказанного следует надеяться, что будет легко понять, почему free(*x) в func2 также сделает недействительной z из функции main.

Теперь интересная часть, которая почему память, на которую указывает z в функции main, кажется, изменилась: кажется, это причуды распределения памяти в вашей системе, где он сопоставляет новое выделение с тем же расположением, что и старое выделение. Важным моментом является то, что z все еще недействителен.

0 голосов
/ 06 апреля 2020

Вы, кажется, думаете, что использование char *x (один указатель) в качестве аргумента для func1 было ошибкой. Тем не менее, я думаю, что это прекрасно. func1 ожидает указатель на символ в качестве аргумента или, в основном, адрес памяти, который при разыменовании дает символ или группу символов. Когда вы пишете func1(x); в main, вы передаете 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? Потому что мы передали базовый адрес этого массива. Мы можем разыменовать этот адрес, чтобы добраться до массива и делать с ним все, что захотим.

...