Передача указателя указателя в C - PullRequest
0 голосов
/ 01 мая 2020

В настоящее время я изучаю, как работать с указателями в C, и у меня есть несколько вопросов, если вы не возражаете.

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

Мои вопросы:

  1. Я знаю, что если я создаю эти файловые указатели внутри метода - они хранятся внутри стек. Является ли проблематичным c (в некотором смысле это может привести к нежелательному поведению) отправлять указатели из памяти стека другим методам?

  2. Я знаю, что если я отправлю указатели файлов на другие методы, он фактически создаст их копию, а это значит, что изменение их внутри метода вообще ничего не изменит.

Вот почему я спрашиваю о " указатели на указатели ", я прочитал (но не совсем понял), что я могу отправлять указатели вспомогательных методов на указатели файлов - и таким образом я буду работать с моими фактическими файлами, а не с какой-нибудь локальной копией.

Допустим, mainFunction - это мой основной метод (он не в "реальном" основном методе), который я использую для обработки файлов

void helperFunction1(FILE* file1, FILE* file2)
{
        fclose(outputFile);
        fclose(inputFile);
}

void helperFunction2(FILE* file1, FILE* file2)
{
     **write some stuff into both files**
}

void mainFunction()
{
    FILE* inputFile = fopen(filePath, "r");
    FILE* outputFile = fopen(OUTPUT_FILE_NAME, "w");
    helperFunction2(inputFile, outputFile);
    helperFunction1(inputFile, outputFile);
}

Будет ли "настоящий" inputFile и outputFile быть закрытым от звонка на helpFunction1?

Изменились бы "настоящие" inputFile и outputFile (записать в них некоторые вещи) от звонка на helpFunction2?

Я бы хотел получить представление и ответы на мои вопросы дополнений (надеюсь, они действительны и не слишком настойчивы), а также мне хотелось бы получить краткое объяснение того, как работать с «указателями на указатели» для изменения данных, а не для изменения некоторых их копий.

Ответы [ 3 ]

1 голос
/ 01 мая 2020

TL / DR - Для того, что вы пытаетесь сделать, helperFunction1 и helperFunction2 объявлены правильно, вам просто нужно использовать правильные имена в helperFunction1.

Более длинная версия

Если вы хотите, чтобы ваша функция изменила сам объект-указатель , то вы должны передать указатель на этот указатель. Вот надуманный пример, в котором мы хотим обновить объект FILE *:

void open( FILE **ptr, const char *name, const char *mode )
{
  *ptr = fopen( name, mode );
}

int main( void )
{
  FILE *in;   // in stores the address of a FILE object
  FILE *out;  // out stores the address of a FILE object

  open( &in, "input.txt", "r" );   // we are changing the values of in and out
  open( &out, "output.txt", "w" ); // so we must pass pointers to those objects
  ...
}

Мы хотим, чтобы open изменил объекты in и out, поэтому мы передаем указатели на эти объекты.

Сравните это с чем-то вроде этого:

void write( FILE *ptr, const char *text )
{
  fwrite( ptr, "%s", text );
}

В этом случае мы не пытаемся изменить сам указатель файла, мы просто пытаемся записать в FILE поток, на который он указывает, поэтому нам не нужно беспокоиться о указателе на указатель в этом случае. Другой надуманный пример:

int main( void )
{
  FILE *out;
  ...
  write( out, "some text" ); // we are not changing the value of out,
  ...                        // so we do not pass a pointer to it
}

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

void foo( T *ptr )
{
  *ptr = new_T_value(); // write a new value to the thing ptr points to
}

int main( void )
{
  T var;
  foo( &var ); // write a new value to var
}

Это работает точно так же для типов указателей - замените T на P *:

void foo( P * *ptr ) // or just P **ptr
{
  *ptr = new_Pstar_value(); // write a new *pointer* value to the thing ptr points to
}

int main( void )
{
  P * var;
  foo( &var ); // write a new value to var
}

Опять же, это верно только в том случае, если вы хотите изменить значение из var.

Вы можете go с еще более высоким уровнем косвенности - замените P на Q *:

void foo( Q * * *ptr ) // or just Q ***ptr
{
  *ptr = new_Qstarstar_value(); // write a new *pointer* value to the thing ptr points to
}

int main( void )
{
  Q * * var;   // or just Q **var
  foo( &var ); // write a new value to var
}

Выражение *ptr в foo имеет тот же тип, что и выражение var в main, поэтому запись в *ptr эквивалентна записи в var.

1 голос
/ 01 мая 2020

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

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

Так что да, вы работаете с исходным ФАЙЛОМ.

0 голосов
/ 01 мая 2020

Вот краткий пример, иллюстрирующий разницу между передачей FILE * по значению (для его использования) и передачей его по адресу (для повторного присвоения).

В use_file(), FILE * само по себе не изменяется, но состояние (непрозрачного) FILE, на которое он указывает, изменяется в результате выполненной операции.

В change_file() мы можем изменить переменную fp в main() потому что мы знаем его адрес.
Таким образом, fp не указывает на одно и то же (непрозрачное) FILE до и после вызова change_file().

/**
  gcc -std=c99 -o prog_c prog_c.c \
      -pedantic -Wall -Wextra -Wconversion \
      -Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
      -g -O0 -UNDEBUG -fsanitize=address,undefined

  $ ./prog_c 
  $ cat output_1.txt 
  first line from main()
  something new from use_file()
  about to close in change_file()
  $ cat output_2.txt 
  just open from change_file()
  second line from main()
**/

#include <stdio.h>

void
use_file(FILE *f)
{
  fprintf(f, "something new from %s()\n", __func__);
}

void
change_file(FILE **inout_f)
{
  FILE *f=*inout_f; // load inout-parameter
  fprintf(f, "about to close in %s()\n", __func__);
  fclose(f);
  f=fopen("output_2.txt", "w");
  fprintf(f, "just open from %s()\n", __func__);
  *inout_f=f; // store out-parameter
}

int
main(void)
{
  FILE *fp=fopen("output_1.txt", "w");
  fprintf(fp, "first line from %s()\n", __func__);
  use_file(fp);
  change_file(&fp);
  fprintf(fp, "second line from %s()\n", __func__);
  fclose(fp);
  return 0;
}

(примечание: без проверки для всего плохого здесь, это просто иллюстративно)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...