Подтвердить что ты не робот

Указатели как аргументы функции в C

Если бы у меня был этот код, например:

int num = 5;
int *ptr = #

В чем разница между следующими двумя функциями?

void func(int **foo);
void func(int *foo); 

Где я вызываю функцию:

func(&ptr); 

Я понимаю, что первый из двух берет указатель на указатель в качестве параметра, а второй принимает только указатель.

Если я перехожу в func(&ptr), я эффективно передаю указатель. Какая разница, указывает ли указатель на другой указатель?

Я считаю, что последнее даст предупреждение о несовместимости, но кажется, что детали не имеют значения, пока вы знаете, что делаете. Кажется, что, возможно, ради удобочитаемости и понимания первый вариант является лучшим вариантом (2-звездный указатель), но с логической точки зрения, в чем разница?

4b9b3361

Ответ 1

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

Пропустить по значению: void fcn(int foo)

При передаче по значению вы получаете копию значения. Если вы измените значение в своей функции, вызывающий объект все еще видит исходное значение независимо от ваших изменений.

Пропустить указатель на значение: void fcn(int* foo)

Передача по указателю дает вам копию указателя - она ​​указывает на то же место памяти, что и оригинал. Это место памяти, где хранится оригинал. Это позволяет вам изменить значение, указанное на указателе. Однако вы не можете изменить фактический указатель на данные, поскольку вы получили только копию указателя.

Пропустить указатель на указатель на значение: void fcn(int** foo)

Вы обойдете это, передав указатель на указатель на значение. Как и выше, вы можете изменить значение, чтобы вызывающий вызывал изменение, потому что он использовал ту же ячейку памяти, что и код вызывающего абонента. По той же причине вы можете изменить указатель на значение. Это позволяет делать такие вещи, как выделение памяти внутри функции и ее возврат; &arg2 = calloc(len);. Вы по-прежнему не можете изменить указатель на указатель, так как это то, что вы получаете копию.

Ответ 2

Разница просто указана в операциях, с которыми процессор будет обрабатывать код. само значение является просто адресом в обоих случаях, thats true. Но по мере того, как адрес получает разыменование, важно для процессора, а также для компилятора, знать после разыменования, с чем он будет обрабатывать.

Ответ 3

Если бы у меня был этот код, например:

int num = 5;
int *ptr = #

В чем разница между следующими двумя функциями?:

void func(int **foo);
void func(int *foo);

Первый хочет указатель на указатель на int, второй хочет указатель, который непосредственно указывает на int.

Где я вызываю функцию:

func(&ptr);

Поскольку ptr является указателем на int, &ptr является адресом, совместимым с int **.

Функция, принимающая int *, будет делать что-то другое, как с int **. Результат разговора будет совершенно иным, что приведет к поведению undefined, возможно, приведет к сбою.

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

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

В adr2 мы имеем значение int, 42.

В adr1 мы имеем адрес adr2, имеющий размер указателя.

&ptr дает нам adr1, ptr, имеет значение &num, которое является adr2.

Если я использую adr1 как int *, adr2 будет неправильно обрабатываться как целое число, что приведет к (возможно, большому) числу.

Если я использую adr2 как int **, первое разыменование приводит к 42, что будет неправильно интерпретировано как адрес и, возможно, приведет к сбою программы.

Это больше, чем просто оптика, чтобы иметь разницу между int * и int **.

Я считаю, что последнее даст предупреждение о несовместимости,

... который имеет смысл...

но кажется, что детали не имеют значения, пока вы знаете, что делаете.

Вы?

Кажется, что, возможно, для удобства чтения и понимания первый вариант является лучшим вариантом (2-звездный указатель), но с логической точки зрения, в чем разница?

Это зависит от того, что делает функция с указателем.

Ответ 4

Существуют две основные практические различия:

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

  • Более принципиально, компилятор должен знать тип, на который указывают, чтобы разыменовать. Например, при разыменовании a double * компилятор будет интерпретировать (в реализации, где double потребляет 8 байтов), 8 байтов, начиная с места памяти, как значение double. Но при 32-битной реализации при разыменовании a double ** компилятор будет интерпретировать 4 байта, начиная с этого места, в качестве адреса другого двойника. При разыменовании указателя тип, на который указывает, является единственной информацией, которую компилятор имеет о том, как интерпретировать данные по этому адресу, поэтому знание точного типа является критическим, и именно поэтому было бы ошибкой думать "они все просто указатели, так что разница"?

Ответ 5

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

void func1 (int *foo) {
    foo = malloc (sizeof (int));
}

int a = 5;
func1 (&a);

похож на

void func2 (int foo) {
    foo = 12;
}

int b = 5;
func2 (b);

В том смысле, что foo может быть равен 12 в func2(), но когда функция func2() вернется, b будет по-прежнему равна 5. В func1(), foo указывает на новый int, но a по-прежнему a, когда функция func1() возвращается.

Что делать, если мы хотим изменить значение a или b? WRT b, нормальный int:

void func3 (int *foo) {
    *foo = 12;
}    

int b = 5;
func2 (&b);

Будет работать - заметьте, нам нужен указатель на int. Чтобы изменить значение в указателе (т.е. Адрес int, на который указывает, а не только значение в int, на которое указывает):

void func4 (int **foo) {
    *foo = malloc (sizeof (int));
}

int *a;
foo (&a);

'a' теперь указывает на память, возвращаемую malloc в func4(). Адрес &a - это адрес a, указатель на int. Указатель int содержит адрес int. func4() берет адрес указателя int, чтобы он мог поместить адрес int в этот адрес так же, как func3() принимает адрес int, чтобы он мог поместить в него новое значение int.

То, как используются разные стили аргументов.