Вернуть `struct` из функции в C
Сегодня я учил пару друзей, как использовать C struct
s. Один из них спросил, не могли бы вы вернуть struct
из функции, на которую я ответил: "Нет! Вы бы вернули указатели на динамически malloc
издание struct
вместо этого."
Исходя из кого-то, кто в основном делает C++, я ожидал, что не сможет вернуться struct
с ценностями. В C++ вы можете перегрузить operator =
для ваших объектов и имеет полный смысл иметь функцию для возврата вашего объекта по значению. В C, однако, у вас нет такой опции, и поэтому я подумал, что на самом деле делает компилятор. Учтите следующее:
struct MyObj{
double x, y;
};
struct MyObj foo(){
struct MyObj a;
a.x = 10;
a.y = 10;
return a;
}
int main () {
struct MyObj a;
a = foo(); // This DOES work
struct b = a; // This does not work
return 0;
}
Я понимаю почему struct b = a;
не должен работать - вы не можете перегрузить operator =
для вашего типа данных. Как это так a = foo();
компилируется нормально? Означает ли это что-то кроме struct b = a;
? Может быть, вопрос, который нужно задать: что именно return
заявление в сочетании с =
знак сделать?
[править]: Хорошо, я только что указал struct b = a
это синтаксическая ошибка - это правильно и я идиот! Но это делает это еще сложнее! С помощью struct MyObj b = a
действительно работает! Что мне здесь не хватает?
11 ответов
Вы можете вернуть структуру из функции (или использовать =
оператор) без проблем. Это четко определенная часть языка. Единственная проблема с struct b = a
является то, что вы не предоставили полный тип. struct MyObj b = a
будет работать просто отлично. Вы также можете передавать структуры в функции - структура точно такая же, как и любой встроенный тип для целей передачи параметров, возвращаемых значений и назначения.
Вот простая демонстрационная программа, которая выполняет все три операции - передает структуру в качестве параметра, возвращает структуру из функции и использует структуры в операторах присваивания:
#include <stdio.h>
struct a {
int i;
};
struct a f(struct a x)
{
struct a r = x;
return r;
}
int main(void)
{
struct a x = { 12 };
struct a y = f(x);
printf("%d\n", y.i);
return 0;
}
Следующий пример почти такой же, но использует встроенный int
Тип для демонстрационных целей. Две программы имеют одинаковое поведение в отношении передачи по значению для передачи параметров, назначения и т. Д.:
#include <stdio.h>
int f(int x)
{
int r = x;
return r;
}
int main(void)
{
int x = 12;
int y = f(x);
printf("%d\n", y);
return 0;
}
При совершении звонка, такого как a = foo();
компилятор может поместить адрес структуры результата в стек и передать ее как "скрытый" указатель на foo()
функция. По сути, это может стать что-то вроде:
void foo(MyObj *r) {
struct MyObj a;
// ...
*r = a;
}
foo(&a);
Однако точная реализация этого зависит от компилятора и / или платформы. Как отмечает Карл Норум, если структура достаточно мала, она может быть полностью передана обратно в регистр.
Насколько я помню, первые версии C позволяли возвращать только те значения, которые могли бы поместиться в регистр процессора, что означает, что вы могли возвращать только указатель на структуру. То же ограничение применяется к аргументам функции.
Более поздние версии позволяют передавать большие объекты данных, такие как структуры. Я думаю, что эта функция была распространена уже в восьмидесятых или начале девяностых.
Массивы, однако, все еще могут быть переданы и возвращены только как указатели.
struct b
строка не работает, потому что это синтаксическая ошибка. Если вы расширите его, включив тип, он будет работать нормально
struct MyObj b = a; // Runs fine
То, что C делает здесь, по сути memcpy
от исходной структуры до места назначения. Это верно как для назначения, так и для возврата struct
значения (и действительно любое другое значение в C)
Нет проблем с передачей структуры. Будет передано по значению
Но что, если структура содержит какой-либо член, который имеет адрес локальной переменной
struct emp {
int id;
char *name;
};
struct emp get() {
char *name = "John";
struct emp e1 = {100, name};
return (e1);
}
int main() {
struct emp e2 = get();
printf("%s\n", e2.name);
}
Теперь здесь e1.name содержит адрес памяти, локальный для функции get(). Как только get () вернет, локальный адрес для имени будет освобожден. Итак, в вызывающей стороне, если мы пытаемся получить доступ к этому адресу, это может вызвать ошибку сегментации, так как мы пытаемся освободить адрес. Это плохо..
Где e1.id будет совершенно действительным, так как его значение будет скопировано в e2.id
Поэтому мы всегда должны стараться не возвращать адреса локальной памяти функции.
Все, что является неправильным, может быть возвращено как и когда угодно
Да, возможно, мы можем передать структуру и вернуть структуру. Вы были правы, но на самом деле вы не передали тип данных, который должен быть таким, как эта структура MyObj b = a.
На самом деле, я также узнал, когда пытался найти лучшее решение для возврата более одного значения для функции без использования указателя или глобальной переменной.
Теперь ниже приведен пример для того же, который рассчитывает отклонение оценок ученика от среднего.
#include<stdio.h>
struct marks{
int maths;
int physics;
int chem;
};
struct marks deviation(struct marks student1 , struct marks student2 );
int main(){
struct marks student;
student.maths= 87;
student.chem = 67;
student.physics=96;
struct marks avg;
avg.maths= 55;
avg.chem = 45;
avg.physics=34;
//struct marks dev;
struct marks dev= deviation(student, avg );
printf("%d %d %d" ,dev.maths,dev.chem,dev.physics);
return 0;
}
struct marks deviation(struct marks student , struct marks student2 ){
struct marks dev;
dev.maths = student.maths-student2.maths;
dev.chem = student.chem-student2.chem;
dev.physics = student.physics-student2.physics;
return dev;
}
Вы можете назначить структуры в C. a = b;
действительный синтаксис.
Вы просто удалили часть типа - тег struct - в своей строке, которая не работает.
struct emp {
int id;
char *name;
};
struct emp get() {
char *name = "John";
struct emp e1 = {100, name};
return (e1);
}
int main() {
struct emp e2 = get();
printf("%s\n", e2.name);
}
отлично работает с новыми версиями компиляторов. Как и идентификатор, содержимое имени копируется в назначенную структурную переменную.
В C нет ссылочных типов, все является значением!
Плохой пример
Этот код работает, но это не тот способ, которым вам следует это делать, поскольку функция может скрыть, выделяется ли структура в стеке или вручную в куче. Это также может привести к увеличению производительности, поскольку структура может быть скопирована во время процесса возврата.
#include <stdio.h>
struct MyStruct{
int a;
};
struct MyStruct MyFunction(){
struct MyStruct someData;
someData.a = 10;
return someData;
}
int main(){
struct MyStruct firstData;
struct MyStruct secondData;
firstData = MyFunction();
printf("first %d\n", firstData.a);
secondData = firstData;
printf("second %d\n", secondData.a);
return 1;
}
Выход:
первые 10
вторые 10
Хороший пример
Вместо того, чтобы создавать структуру внутри функции и возвращать ее, рекомендуется передавать указатель на структуру внутри функции. Это позволяет потребителю функции реализовать полностью прозрачное управление памятью этого поля. Обычно структуры содержат большие объемы данных, и они часто распределяются динамически. Это также имеет то преимущество, что вы можете использовать возвращаемое значение функции, чтобы предоставить вызывающей стороне дополнительную информацию о результате выполнения в стиле флага состояния (например, 0=ОК, 1=ошибка и т. д.). Более того, работа с указателями также более производительна, поскольку нет необходимости копировать всю структуру, чтобы передать ее в функцию в качестве значения.
Помимо влияния на производительность передачи структур по значению, существует также ловушка, когда функция будет изменена на динамическое выделение структуры, при этом вызывающий код не будет изменен для освобождения памяти вручную, что приведет к утечке памяти.
#include <stdio.h>
#include <stdlib.h>
struct MyStruct{
int a;
};
int MyFunction(struct MyStruct * pointer){
pointer->a = 10;
return 0;
}
int main(){
struct MyStruct* firstData;
int statusFlag;
firstData = malloc(sizeof(struct MyStruct));
statusFlag = MyFunction(firstData);
if(statusFlag == 0){
printf("OKAY %d\n", firstData->a);
}
else{
printf("ERROR!\n");
}
free(firstData);
return 0;
}
Выход:
ОКЕЙ 10
Адрес struct var e2 передается как аргумент в стек вызываемого объекта, и там назначаются значения. Фактически, get() возвращает адрес e2 в eax reg. Это работает как вызов по ссылке.
#include <stdio.h>
struct emp {
int id;
char *name; /* This must point to valid memory, or replace with an array that holes the data, like this char name[128] */
};
struct emp bad() {
static char name[] = {'J', 'o', 'h', 'n', '\0'}; /* static enforces this array to be stored globally and not in the local stack which would not be valid after the function returns */
struct emp e1 = {404, name};
return (e1);
}
int main() {
struct emp e2 = bad();
printf("%s\n", e2.name);
}