Последнее обновление:
Способы передачи значения функции в языке С (Linux)
У новичков иногда возникает сложность при передаче (получении) значения функции в главную функцию. Например, есть такой код:
main {
x = Func(y);
……
}
Func(z){
…
return S;
}
В языках высокого уровня, например, в С++, не говоря уже о Питоне, РНР и т.д., указанная выше конструкция работает вообще без каких-либо проблем, в результате переменная х
получает значение, равное S
. А вот в С – не все так просто. Дело в том, что S
является локальной переменной, а она в С существует ровно до того момента, пока не закончила работу функция, внутри которой она объявлена (в данном случае, функция Func(z)
). Поэтому после выхода из нее S
может оказаться пустым или иметь, вообще говоря, произвольное значение. Или даже может вызвать ошибку сегментации в процессе выполнения программы. Это называется – неопределенное поведение функции.
Кстати говоря, это верно не только для функций, а и вообще для любых участков программного кода, который заключен между фигурными скобками {… }
, например, для цикла. Поэтому в языке С (в отличие от С++, где с этим несколько проще) необходимо тщательно следить за областью хранения переменных. Ниже приведены характерные примеры корректного возврата результатов работы функций в основную программу.
#include<stdio.h> | |
int *func(); | |
int *func1(int **k); | |
int func2(int **k, int i); // Объявлена по значению | |
int *func3(int **k, int *i); | |
int main() | |
{ | |
int *i; | |
i = func(); | i=18 |
printf("func()=%d\n",*i); | func()=18 |
func1(&i); | i=17*2=34 |
printf("func1(&i)=%d \n",*i); | func1(&i)=34 |
printf("func1(&i)=%d func2(&i, *i)=%d\n",*i, func2(&i, *i)); | func1(&i)=85 func2(&i, *i)=34+17*3=85 |
func3(&i, i); | i=85+10=95 |
printf("func3(&i, i)=%d\n",*i); | func3(&i, i)=95 |
return 0; | |
} | |
int *func() // Возврат статической локальной переменной по ссылке | |
{ | |
static int k = 18; | |
return(&k); | |
} | |
int *func1(int **k) // Возврат указателя | |
{ | |
**k = 17*2; | |
return *k; | |
} | |
int func2(int **k, int i) // Возврат указателя на указатель | |
{ | |
**k = 17*3+ i; | |
return **k; | |
} | |
int *func3(int **k, int *i) // Возврат указателя на указатель без функции использования return |
|
{ | |
**k = *i+10; | |
} |
Вот результат работы программы:
func()=18
func1(&i)=34
func1(&i)=85 func2(&i, *i)=85
func3(&i, i)=95
Пояснения
Пример func()
Самый простой способ – это передача значения функции по ссылке и инициализация возвращаемой ею переменной как static
. Это позволяет сохранить ее значение и после выхода из функции.
Пример func1()
Эта функция передает указатель, но при этом сама тоже возвращает указатель. В итоге получается передача указателя на указатель. При этом передается не значение функции, а переменная i
. Так как значение этой переменной передается в функцию по ссылке, это означает, что функция с ней будет осуществлять некие действия, в результате чего переменная может принять другое значение, причем оно будет доступно как в самой этой функции, так и в main()
. В данном случае значению переменной (внутри функции func1()
) присваивается величина произведения 17*2, т.е. 34. Так как больше ничего эта функция не делает, по возвращению из нее переменная i
сохраняет значение, равное 34. Именно поэтому строчка return *k;
является НЕОБЯЗАТЕЛЬНОЙ, если нас не интересует значение, возвращаемое этой функцией.
Пример func2()
Здесь производится передача функции переменной i
и по ссылке, и через указатель. Как ни странно, на первый взгляд, func1(&i)=85
, а не 34. Значение 34 получится только, если убрать из printf()
вызов функции func2(&i, *i)
.
Почему так? Вначале i
, в самом деле, равна 34. Потом происходит вызов func2(&i, *i)), при этом, будучи вызванной по ссылке, i принимает значение 17*3=51. К этому значению добавляется величина указателя i
, которое равно 34 (именно оно было передано по указателю). Поэтому в итоге получается 34+51=85. Иными словами, в этом примере переменная i
играет двоякую роль: с одной стороны, она передается по ссылке и над нею делаются некие действия, в результате чего она становится равной 51. А, с другой стороны, она вызывается по указателю (при этом берется ее значение, которое было определено ранее, составляющее 34). Результаты складываются и полученное значение (85) присваивается указателю на указатель на переменную k
, который, в свою очередь, передается в окончательное значение переменной i
при возврате в main()
, т.к. **k
в самой функции func2()
соответствует &i
в вызове func2(&i, *i)
.
Команда printf()
выводит на экран указатель на i
и результат func2(&i, *i)).
Однако, вывод она делает уже после того, как известны все ее аргументы. В самый последний момент, уже перед выводом, значение i заменяется с 34 (старое) на 85 (новое) – так как, еще раз, ее значение передавалось по ссылке в первом аргументе функции func2()
. Именно оно и отражается на экране.
Пример func3()
Этот пример аналогичен предыдущему. Так как переменная i
передается в функцию func3()
по ссылке, то ее значение будет установлено тому, какое получится в результате работы этой функции. А она прибавляет к переданной i
значение 10. Следовательно, после срабатывания вызова func3(&i, i);
значение переменной i
получается равным 85+10=95 и именно оно затем выводится на экран.
Выводы
Таким образом, передача значения по ссылке может характеризоваться, как «обратная передача» или «неявная передача». Это когда вызываемая функция меняет значение переменной и оно уже передается в основной модуль. Т.е. при этом используется не значение функции, а значение ее аргумента (переменная i
). Именно поэтому в подобных случаях вызов return
в функции - необязателен.
Тогда как при вызове по значению (или по указателю) меняется именно значение функции и оно уже возвращается в основной модуль. Это – как раз то самое значение, которое возвращается вызовом return
, без него функция не сможет вернуть ничего и, как правило, ее работа в таком случае завершается ошибкой сегментации.
А также есть еще динамическая память
Кроме того, можно передать значение функции и при помощи динамической памяти, выделяемой в куче (heap). Это делается при помощи функций, например, malloc()
, realloc()
примерно следующим образом:
char *i;
if (!(i=( char *)malloc(sizeof(char)*1000))) {
printf("Allocation error.");
exit(0);
В данном случае выделяется динамическая память в размере 1000 символов для переменной, точнее, для строкового массива i
, имеющего тип char
. Выделять динамическую память следует внутри функции, которая ее использует, но ДО того, как она используется в первый раз. При этом объем выделенной памяти должен быть не ниже, чем объем массива, иначе получится уже упомянутое выше неопределенное поведение программы, которое в некоторых случаях завершается ошибкой сегментации.
После того, как переменная более не используется, ее рекомендуется освободить при помощи команды free(i)
;. Так как доступ к динамической памяти возможен как из функции, так и из главного модуля main()
, то значение переменной, полученное в результате работы функции, можно возвращать в обычном виде (близком к тому, как это делается в высокоуровневых языках программирования):
int *func4() // Возврат указателя на указатель
{char *i;
if (!(i=( char *)malloc(sizeof(char)*1000))) {
printf("Allocation error.");
exit(0);
i = "177";
return i;
}
При этом вызов из главного модуля видаp = func4();
передаст, после выполнения функции func4()
, в массив р
строку символов "177".