Запрос с двойными указателями в C

В университете мы делали такой код:

int cargaArreglo(int ** A);

int main(){

int * A = NULL;
int i = 0;
int dimension = cargaArreglo(&A);

for( i = 0; i < dimension; i++ )
     printf( "[%d]", A[i] );

free( A );
return 0;
}

int cargaArreglo(int ** A){

int i = 0;
char bandera = 's';
(*A) = malloc( 1*sizeof(int) );

while( bandera != 'n' ){
     printf( "Ingrese un numero" );
     scanf( "%d", &(*A)[i] );
     printf( "Desea seguir ingresando?" );
     i++;
     fflush( stdin );
     scanf( "%c", &bandera );
     if( bandera != 'n' )
     (*A) = realloc( (*A), (i+1)*sizeof(int) );
}
return i;
}

Мы пытались загрузить динамический массив и добавлять значения без ограничений. Код работает очень хорошо, но в функциях нам нужно объявить параметры с помощью двойных указателей, есть ли возможность преобразовать это в простой указатель? Я имею в виду, учитель сказал, что это невозможно, потому что без двойного указателя я не могу изменить значение глобальной переменной. Это заставило меня запутаться в отношении указателей.

Спасибо за ваше время!

ПД: Если вы найдете ошибку, уважайте мой английский, пожалуйста, сообщите мне, потому что я очень помог бы мне в учебе.


person Nicolas Mozo    schedule 18.05.2016    source источник
comment
Nitpick: На самом деле это не двойной указатель, это указатель на указатель. :)   -  person Sourav Ghosh    schedule 18.05.2016
comment
fflush( stdin ); — это неопределенное поведение.   -  person Lundin    schedule 18.05.2016
comment
См.: stackoverflow.com/questions/15244429/   -  person Jite    schedule 18.05.2016
comment
В представленном коде нет глобальных переменных. Переменные, объявленные в main(), являются локальными переменными этой функции.   -  person John Bollinger    schedule 18.05.2016
comment
Связано: stackoverflow.com/questions/5580761/   -  person John Bollinger    schedule 18.05.2016


Ответы (3)


Если вам не нравятся указатели на указатели, вы всегда можете изменить сигнатуру функции, чтобы она возвращала новое значение указателя для присвоения A и вместо этого устанавливала значение измерения с помощью указателя:

[...]
int dimension;
int * A = cargaArreglo(&dimension);
[...]


int * cargaArreglo(int * retDimension){
   int i = 0;
   char bandera = 's';
   int * B = malloc( 1*sizeof(int) );

   while( bandera != 'n' ){
        printf( "Ingrese un numero" );
        scanf( "%d", &B[i] );
        printf( "Desea seguir ingresando?" );
        i++;
        fflush( stdout ); /* fflush( stdin ) is UB */
        scanf( "%c", &bandera );
        if( bandera != 'n' )
           B = realloc( B, (i+1)*sizeof(int) );
   }
   *retDimension = i;

   return B;
}

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

Учитель имел в виду, что когда вы вызываете функцию на C, временная копия каждого аргумента помещается в стек для использования внутри функции. Поэтому, если вы измените значение аргумента внутри вызываемой функции, оно не изменит значение обратно в вызывающей функции... например, запустив этот код:

void foo(int x, int * A)
{
   x = 6;
   A = NULL;
}

int x = 5;
int * A = &x;
printf("BEFORE x=%i A=%p &x=%p\n", x, A, &x);
foo(x, A);
printf("AFTER x=%i A=%p &x=%p\n", x, A, &x);

... выведет:

BEFORE x=5 A=0x2345678 &x=0x2345678
AFTER x=5 A=0x2345678 &x=0x2345678

... что означает, что foo() на самом деле не изменил значение ни x, ни A в вызывающей функции, потому что foo() изменил только копию x и копию A, которые были переданы ему, а не исходные переменные.

Однако foo() может использовать указатель A для (косвенного) изменения значения x вызывающей функции, потому что копия A функции по-прежнему указывает на x вызывающей функции, поэтому вы можете реализовать foo для этого :

void foo(int * A)
{
   *A = 666;
}

int x = 5;
printf("BEFORE x=%i\n", x);
foo(&x);
printf("AFTER x=%i\n", x);

... и вы получите этот вывод:

BEFORE x=5
AFTER x=666

... и если вышеизложенное имеет для вас смысл, то случай указателя на указатель на самом деле точно такой же, как и выше, за исключением того, что вместо A, указывающего на int, тип значения, на которое он указывает указатель:

void foo(int ** x)
{
    *x = NULL;
}

int y = 5;
int * x = &y;
int ** A = &x;
printf("BEFORE x=%p\n", x);
foo(A);
printf("AFTER x=%p\n", x);

... распечатает:

BEFORE x=0x2345845  (actual value will vary depending on your computer)  
AFTER x=NULL
person Jeremy Friesner    schedule 18.05.2016

существует ли возможность преобразовать это в простой указатель?

Это это простой указатель. C не имеет другого вида.

Понимание этого момента является одним из ключей к мастерству в C. Другой способ взглянуть на это состоит в том, что указатели — это всего лишь один из видов значений, поддерживаемых C. Действительные значения указателя имеют значение как адреса (в некотором смысле) функций или объектов в программе, но это никоим образом не делает их магическими. Это первоклассные ценности. В частности, указатели могут указывать на объекты типа указателя, что является естественным следствием системы типов языка. То, что указатель указывает на другой указатель, не делает его непростым.

Я думаю, что некоторых людей смущают указатели, потому что они читают тип слева направо, поэтому они сначала видят имя некоторого базового типа (int в вашем случае) и придают ему наибольшее значение. Но хотя int *, int **, и т. д. типы связаны с int типом, значения этих типов указателей вообще мало связаны с int. Вместо того, чтобы читать типы слева направо, вы должны читать их изнутри наружу, что во многих распространенных случаях происходит справа налево. Чтение int **A как «A является указателем на указатель на int» подчеркивает, что его значение является указателем, что является наиболее важным в нем.

Что касается конкретных способов реструктуризации вашей программы, @JeremyFriesner уже очень хорошо рассмотрел этот вопрос. Главное, что нужно понять в этой области, — это то, почему вы (можете) захотеть использовать аргумент указателя в первую очередь.

В общем, есть несколько причин, по которым вы можете захотеть это сделать, но главная и применимая к вашему конкретному коду — это та, которую дал вам ваш инструктор: чтобы функция могла модифицировать объект, объявленный в другой области или выделенный динамически. В этом случае этот объект является указателем на адрес вашего динамически выделяемого массива. (Помните: указатели — это самостоятельные объекты.)

Кроме того, функции C имеют только одно возвращаемое значение. Если вы хотите, чтобы функция сообщала вызывающей стороне несколько фрагментов данных, то либо вы должны предоставить аргументы-указатели, с помощью которых она может это сделать, либо вы должны вернуть значение подходящего типа структуры (которое вы, возможно, еще не изучили), или вы должны вернуть указатель на структуру или массив, содержащий все данные.

person John Bollinger    schedule 18.05.2016

Если вам не нравится, как выглядит синтаксис, вы можете использовать локальную переменную-указатель при выполнении работы внутри функции и присвоить ее обратно разыменованному указателю на указатель в конце:

int cargaArreglo(int **A){

    int i = 0;
    char bandera = 's';
    int *B;
    B = malloc( 1*sizeof(int) );

    while( bandera != 'n' ){
         printf( "Ingrese un numero" );
         scanf( "%d", &B[i] );
         printf( "Desea seguir ingresando?" );
         i++;
         fflush( stdin );
         scanf( "%c", &bandera );
         if( bandera != 'n' )
         B = realloc( B, (i+1)*sizeof(int) );
    }
    *A = B;
    return i;
}
person dbush    schedule 18.05.2016