Почему непустая пустая функция C без оператора return должна копировать структуру?

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

Следуя второму ответу на Глубокое копирование структур с массивами символов в C (как скопировать массивы?), я придумал следующий пример :

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>

#define THE_ARRAY_SIZE 10

struct the_struct
{
    uint64_t the_member;
};

typedef struct the_struct the_struct_type;

the_struct_type *the_function(the_struct_type *the_parameter)
{
    the_struct_type *the_returned_variable = NULL;
    *the_returned_variable = *the_parameter;
    return the_returned_variable;
}

void the_show( the_struct_type *the_parameter)
{
    printf( "the_member is %" PRIu64 "\n", the_parameter->the_member);
}

int main(int argc, char *argv[]) {
    
    the_struct_type the_source;
    the_struct_type *the_target = NULL;
    
    the_source.the_member = 7777777;
    
    the_target = the_function(&the_source);
    the_show(the_target); 
}

В результате получилось:

Segmentation fault (core dumped)

От отчаяния прокомментировал внутренности функции. Как будто невидимая рука руководила моими действиями. :)

the_struct_type *the_function(the_struct_type *the_parameter)
{
    //the_struct_type *the_returned_variable = NULL;
    //*the_returned_variable = *the_parameter;
    //return the_returned_variable;
}

И неожиданно результат оказался желаемым - хотя я не уверен, что я ожидал:

the_member is 7777777

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

int main (int argc, char *argv[]) {
    
    /* */
    
    the_struct_type *the_array_of_struct_type = calloc(THE_ARRAY_SIZE, sizeof *the_array_of_struct_type);
    the_array_of_struct_type[0] = *the_target;
    the_show( &the_array_of_struct_type[0] );

    the_source.the_member = 123456;
    the_target = the_function( &the_source );
    the_show(the_target);
    
    the_array_of_struct_type[1] = *the_target;
    the_show( &the_array_of_struct_type[1] );    
    the_show( &the_array_of_struct_type[0] );
}

В результате чего:

the_member is 7777777
the_member is 7777777
the_member is 123456
the_member is 123456
the_member is 7777777

Из первого ответа на пустой возврат в непустой функции, является ли неопределенное поведение? Я понимаю, что это ограничение нарушение, но в результате эта непустая пустая функция C без оператора return глубоко копирует структуру ввода в структуру вывода.

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

Я предполагаю, что компилятор является ключевым здесь: я использую gcc 7.4.0 под Cygwin x86_64 3.0.7 (0.338 / 5/3) и Windows 10. Я компилирую кратко:

> gcc code.c -o executable.exe
> gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/7.4.0/lto-wrapper.exe
Target: x86_64-pc-cygwin
Configured with: /cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-7.4.0-1.x86_64/src/gcc-7.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --disable-libssp --enable-libada --disable-symvers --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible --enable-libstdcxx-filesystem-ts
Thread model: posix
gcc version 7.4.0 (GCC)

person DIVVS IVLIVS    schedule 17.07.2020    source источник
comment
Кроме того, ошибка сегментации произошла из-за того, что вы пытаетесь разыменовать и назначить NULL, что является еще одним случаем неопределенного поведения. Если вы хотите скопировать содержимое структуры, сначала должен быть совместимый объект для копирования, который вы не предоставили.   -  person Felix G    schedule 17.07.2020
comment
Использование возвращаемого значения непустой функции, которая упала с конца (т. Е. Не выполнила оператор возврата), действительно является неопределенным поведением. В вашем случае, вероятно, произойдет то, что the_parameter просто интерпретируется как возвращаемое значение (поэтому на самом деле ничего не было скопировано). Просто сравните адрес вашей исходной структуры со скопированной, и вы, вероятно, обнаружите, что они такие же   -  person Felix G    schedule 17.07.2020
comment
@DIVVS IVLIVS При чем здесь the_function функция в ответе Питера? Вы его кардинально изменили.   -  person RobertS supports Monica Cellio    schedule 17.07.2020
comment
@RobertS_supports_Monica_Cellio Ну ... blush это был просто источник вдохновения для моей ленивой попытки создать функцию копирования структуры.   -  person DIVVS IVLIVS    schedule 17.07.2020
comment
@DIVVSIVLIVS функции нужно что-то копировать в. Как бы то ни было, вы пытаетесь скопировать в никуда (а точнее NULL). Если вы хотите, чтобы эта функция копировалась в новый объект, вам нужно сначала использовать malloc() для создания этого объекта (и проверить возвращаемое значение, чтобы убедиться, что это не NULL).   -  person Felix G    schedule 17.07.2020
comment
@DIVVSIVLIVS Я думаю, что могу понять ваши намерения, но на самом деле это не копирование. Когда вы опускаете *s во втором операторе в the_function, все четко определено, но вы просто назначаете адрес одной структуры указателю целевой структуры. На самом деле у вас нет двух структур. У вас есть указатель, который снова ссылается на ту же структуру. Это также будет равно the_struct_type * the_function (the_struct_type * the_parameter) { return the_parameter; }   -  person RobertS supports Monica Cellio    schedule 17.07.2020
comment
Segmentation fault происходит из-за этой *the_returned_variable = *the_parameter; строки. Просто измените его на the_returned_variable = the_parameter.   -  person Shubham    schedule 17.07.2020
comment
@DIVVSIVLIVS Мне немного непонятно, о чем вы спрашиваете, потому что заголовок задает один вопрос, а тело вопроса содержит много несвязанного материала. Ваш вопрос соответствует названию, или вы действительно спрашиваете, как следует правильно реализовать глубокую копию?   -  person 4386427    schedule 17.07.2020
comment
@DIVVSIVLIVS ... и далее: вы пишете о глубокой копии, но в коде нет глубокой копии   -  person 4386427    schedule 17.07.2020
comment
4386427 сделал здесь важный момент: многие люди говорят о глубоком копировании, даже если у них есть просто структура с массивом или другой вложенной структурой. Но настоящая глубокая копия требуется только в том случае, если структура содержит указатели, и в этом случае нет автоматического универсального подхода. Вместо этого вы должны тщательно анализировать каждый указатель, чтобы знать, как правильно сделать копию (если это вообще возможно). Таким образом, разные структуры нуждаются в своих собственных отличных функциях глубокого копирования, которые могут радикально отличаться друг от друга.   -  person Felix G    schedule 17.07.2020


Ответы (2)


Результат: ошибка сегментации (дамп ядра).

Вероятно, это связано с тем, что вы пытаетесь разыменовать NULL указатель the_return_variable и назначить несуществующий заостренный объект первым полем структуры, на которую указывает указатель the_parameter, внутри the_function.

the_struct_type * the_returned_variable = NULL;
*the_returned_variable = *the_parameter;

Разыменование указателя NULL вызывает неопределенное поведение.

От отчаяния прокомментировал внутренности функции. ... И неожиданно результат оказался желаемым.

Результат может быть таким, каким вы хотели его выполнить, но поведение не определено или, как сказал Лундин, его можно даже рассматривать как нарушение ограничений, чтобы использовать возвращаемое значение непустой функции, в которой отсутствует явное return утверждение. Никогда не доверяйте такому поведению.

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

Как сказано выше, это неопределенное поведение / нарушение ограничения. Ни на что нельзя доверять. Это также не копирование содержимого структуры.

Значение указателя parameter, вероятно, рассматривается как возвращаемое значение и просто присваивается указателю the_target в main().

Когда вы пытаетесь распечатать значение предполагаемого скопированного поля в the_show, он фактически печатает только поле исходной структуры.

Почему непустая пустая функция C без оператора return должна копировать структуру?

Этого не должно быть, и на самом деле это не так. Функция Питера сильно отличается от the_function в вашем примере.

void deepCopyPerson (struct person *target, struct person *src)
{
    *target = *src;
}
  1. deepCopyPerson - это функция, возвращающая void.
  2. Вот два параметра (для указателя на пункт назначения (цель) и на источник.
  3. Назначение идет напрямую от источника к месту назначения.

Резюме: это совсем другое.


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

Обратите внимание: если копируемая структура содержит элементы-указатели, то присвоение, как показано в подходе Питера, не будет глубокой копией. Это будет неглубокая копия.

Связанный:

person RobertS supports Monica Cellio    schedule 17.07.2020
comment
@ 4386427 Я тоже читал ответы на связанные вопросы по этому поводу. Кажется, что неглубокая копия - это когда указатели в качестве членов задействованы, а указатели просто копируются один к одному, так что указатели в B относятся к вещам, на которые ссылаются указатели в A, что здесь не так. Все остальное, когда вы копируете только объекты данных или когда вы сначала копируете структуру как мелкую копию, а затем настраиваете указатели в скопированной структуре, кажется глубокой копией. Не уверен, нужно ли копировать и отступы, чтобы их можно было классифицировать как жесткие для глубокого копирования. - person RobertS supports Monica Cellio; 17.07.2020
comment
Я бы сказал, что оба термина следует использовать только для структур, содержащих указатели. Это потому, что оба термина имеют подразумеваемое значение, которое не подходит для обычных структур. Термин «мелкая копия» подразумевает, что копия является неполной, тогда как термин «глубокая копия» - по крайней мере, на мой взгляд - подразумевает, что для копирования требуется какой-то особый подход. Итак, я бы просто использовал термин «копия» для обычных структур, а другие термины - только для структур, содержащих указатели. Это, конечно, только мое личное мнение по этому поводу и не обязательно отражает то, как обычно используются эти термины. - person Felix G; 17.07.2020
comment
@FelixG Да, я думаю, это действительно имеет смысл. - person RobertS supports Monica Cellio; 17.07.2020

Если вы хотите передать struct или union по значению и скопировать его в новый struct, вам не нужно ничего делать.

typedef struct 
{
    char name[500];
    double val[40];
    int z[400];
}my_s_t;

my_s_t foo(my_s_t s)
{
    my_s_t s1 = s;

    s1.z[4]++;

    return s1;
}

my_s_t foo1(my_s_t *s)
{
    my_s_t s1 = *s;

    s1.z[4]++;

    return s1;
}

void bar()
{
    volatile my_s_t arr[10];

    /* ......*/

    arr[2] = foo(arr[3]);
    arr[4] = foo1(arr + 5);
}

https://godbolt.org/z/8MWqGf

person 0___________    schedule 17.07.2020
comment
volatile почему? ... вам не нужно ничего делать: звучит немного странно - в конце концов, код должен делать копию :-) Кроме того, OP, похоже, хочет указатель на копию. - person 4386427; 17.07.2020
comment
@ 4386427 для примера Godbolt для предотвращения optimizatio9ns - person 0___________; 17.07.2020
comment
хорошо, хорошо, но ... не могли бы вы просто указать godbolt на -O0 (и / или удалить volatile при копировании кода) - person 4386427; 17.07.2020
comment
@ 4386427 - -O0 добавит слишком много шума - person 0___________; 17.07.2020