Создание глубокой копии структуры … создание мелкой копии структуры

Есть вопросы, КАК ЭТО, но они не настолько похожи на мой конкретный вопрос ДЛЯ МЕНЯ, чтобы забрать.

Мой вопрос заключается в том, как сделать глубокую копию структуры с указателями в качестве членов и как сделать SHALLOW копию структуры с указателями в качестве членов. А затем, просто для справки, как сделать глубокую копию структуры WITHOUT членов-указателей и как сделать мелкую копию структуры WITHOUT членов-указателей (не уверен, что это последнее имеет смысл).

Допустим, у нас есть это:

typedef struct Student { char* first_name; char* last_name; int grade; long id; } Student; 

Вот общая функция, которую я создал для создания ученика (заголовок трудно форматировать):

 Student* create_student(const char* first_name, const char* last_name, int grade,long id) { Student *newStudentp = (malloc(sizeof(Student))); newStudentp -> last_name = (malloc((strlen(last_name) + 1) * sizeof(char))); newStudentp -> first_name = (malloc((strlen(first_name) + 1) * sizeof(char))); strncpy(newStudentp -> first_name, first_name, strlen(first_name) + 1); strncpy(newStudentp -> last_name, last_name, strlen(last_name) + 1); newStudentp -> grade = grade; newStudentp -> id = id; return newStudentp; } 

Моя попытка сделать глубокую и мелкую копию;

 int main() { Student *s1 = create_Student("Bo","Diddly", 100, 221); Student *s2 = create_Student("Leeroy","Jenkins",50,1337); memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2? return 0; } 

Для глубоких копий структур с элементами указателя я знаю, что мы должны сделать НАШУ СОБСТВЕННУЮ функцию копирования, которая делает что-то разумное с указателями. Какая эта разумная вещь … Я не уверен … так вот моя попытка этой копии DEEP.

 void copy_Student(Student *s1, Student *s2) { s2 -> grade = s1 -> grade; s2 -> id = s1 -> id; s2 -> first_name = s1 -> *first_name; s2 -> last_name = s1 -> *last_name; } 

Другая часть моего вопроса (структуры без указателей как членов), вероятно, может быть просто объяснена в устной форме.

ИЗМЕНИТЬ ПОСЛЕ ЧТЕНИЯ ПОЛЕЗНЫХ КОММЕНТАРИЙ:

Мелкая копия: memcpy (s2, s1, sizeof (Student));

Глубокая копия:

 void free_student(Student* stu) { free(stu -> first_name); free(stu -> last_name); } void copy_Student(Student *s1, Student *s2) { s2 -> grade = s1 -> grade; s2 -> id = s1 -> id; s2 -> first_name = strdup(s1 -> first_name); s2 -> last_name = strdup(s1 -> last_name); } 

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

 Student *s1 = create_Student("Bo","Diddly", 100, 221); Student *s2 = create_Student("Leeroy","Jenkins",50,1337); memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2? 

Если бы у вас был правильный размер, это было бы так же, как s2 = s1; , Но поскольку у вас неправильный размер, он слишком много копирует и перезаписывает все, что есть в памяти после s2. Чтобы сделать реальную мелкую копию, оставьте поле & :

 memcpy(s2,s1,sizeof(Student)); //shallow copy of s1 INTO s2 

Код, который у вас есть для глубокой копии, также неправильный, но вы на правильном пути. Основная идея глубокой копии заключается в том, что вы должны копировать каждое поле; для типов без указателей это то же самое, что и мелкая копия, но для указателей вы должны сделать что-то умнее. Однако код, который вы опубликовали, не делает этого. Попробуйте это вместо этого.

 void copy_Student(Student *s1, Student *s2) { s2 -> grade = s1 -> grade; s2 -> id = s2 -> id; s2 -> first_name = strdup(s1 -> first_name); s2 -> last_name = strdup(s1 -> last_name); } 

Обратите внимание, что во избежание утечек памяти вам также необходимо будет освободить старые имена из s2 до назначения новых копий, создать функцию free_Student , которая освободит эти имена, а также убедитесь, что create_Student копирует имена в первую очередь (или include флаги «бесплатно», поэтому вам не нужно копировать литеральные строки).

Теперь для структуры без указателей (или других ссылочных типов) нет никакой разницы между глубокой и мелкой копией, поскольку структура данных сама по себе неглубока.

Разница между мелкой копией и глубокой копией может быть объяснена в одном предложении: указатели на мелкие копии копий; глубокая копия копирует то, на что указывает.

Чтобы начать с последней части вашего вопроса: если нет указателей, нет никакой разницы между мелкой или глубокой копией.

Ваша попытка сделать мелкую копию технически правильной. Однако это логично неправильно. delete_student() которая освобождает mallocs, не может обрабатывать мелкие копии. Он не знал бы, сколько еще экземпляров учеников все еще существует, и нужно отложить free() до удаления копии alst.

У глубокой копии есть очень сложная проблема. это технически некорректно. Как ни странно, ваша функция create_student показывает, что вы знаете, как скопировать символ * в другой, который имеет глубокую копию как first_name и last_name . Ваш copy_Student должен сделать то же самое.

ОТКАЗ ОТ ОТСУТСТВИЯ: Я предполагаю 64-битный компилятор gcc, что и sizeof (), а также 8-байтовое выравнивание. Я также понимаю, что это почти 7-летний вопрос, но он появился в моем поиске Google как номер 1, поэтому я хотел прояснить некоторые вещи для других, которые могли бы наткнуться на него. На самом деле я просто хотел прокомментировать, но для этого требуется 50 репутации. Итак, вот еще один ответ …

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

Код, который вы указали в качестве мелкой копии, имеет тонкий (но потенциально катастрофический) надзор.

В функции main ():

 Student *s1 = create_Student("Bo","Diddly", 100, 221); Student *s2 = create_Student("Leeroy","Jenkins",50,1337); memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2? 

Локальные (указатель / адрес памяти) переменные s1 и s2 объявляются (в стеке):

  • Student * s1 (8-байтовый адрес памяти на 64-битном gcc)
  • Student * s2 (8-байтовый адрес памяти на 64-битном gcc)

s1 и s2, являющиеся указателями, являются адресами памяти структур студентов, которые распределяются в куче памяти из-за того, что ваша функция create_Student () использует malloc (), которая выделяет память в куче ( куча означает, что она будет придерживаться даже после create_Student ()).

Помещение амперсанда перед s1 или s2 похоже на высказывание: «Дайте мне адрес адреса моей студенческой структуры»,

& s1 и & s2 теперь представляют собой ячейки памяти (в стеке) ваших указателей s1 и s2 (или адресов памяти). Другими словами, теперь у вас есть 2 уровня глубины указателя: указатель на указатель (расположенный в стеке) на структуру (кучу) Студент.

Задав memcpy (& s2, & s1, sizeof (Student)), вы попросили memcpy переписать указатель стека s2 на содержимое (или адрес) указателя стека s1, а также испортить еще 24 байта памяти стека main (), что сразу следует за 8 байтами, начиная с & s2, с 24 байтами, которые сразу следует за & s1. Поэтому, чтобы процитировать Anomie:

Если бы у вас был правильный размер, это было бы так же, как s2 = s1;

Поэтому, используя ту же логику, что «нужно сделать копию того, что указатели указывают на», ваша копия DEEP-копии copy_Student () может выглядеть так:

 // I swapped the s1 and s2 arguments with // target and source for clarity as well as their order // to more closely mimic memcpy() void copy_Student(Student *target, Student *source) { if (target!=NULL) free_Student(target); // if target is already allocated, free it... assert(source != NULL); target->grade = source->grade; target->id = source->id; target->last_name = (malloc((strlen(source->last_name) + 1) * sizeof(char))); target->first_name = (malloc((strlen(source->first_name) + 1) * sizeof(char))); strncpy(target->first_name, source->first_name, strlen(source->first_name) + 1); strncpy(target->last_name, source->last_name, strlen(source->last_name) + 1); } 

Вместо того, чтобы думать об этом как о копии, почему бы вам не создать новую структуру, но с теми же параметрами, что и тот, который вы хотите дублировать? Это тонкая разница, но у вас уже есть код:

 Student *s2 = create_Student("Leeroy","Jenkins",50,1337); Student *wiper = create_Student(s2->first_name, s2->last_name, s2->grade, s2->id); 

структура wiper имеет клон s2 .

Чтобы сделать мелкую копию, сделайте так, как вы делаете с s1 и s2 ( memcpy ), или просто:

 s2 = malloc(sizeof(Student)); *s2 = *s1 
 memcpy(&s2,&s1,sizeof(Student)); //shallow copy of s1 INTO s2? 

Здесь вы перезаписали указатель s2 и указатели в s2 соответствующими значениями указателя в s1 , так что вы пропустили память.

Чтобы выполнить глубокую копию, вы должны сначала освободить память, на которую указывает целевая структура. Затем выделите достаточное количество памяти, чтобы удерживать строки, на которые указывает структура источника. Теперь, strncpy строк.

 void copy_Student(Student *s1, Student *s2) { assert( ( s1 != NULL ) && ( s2 != NULL ) ); if( s2->first_name != NULL ) free( s2->first_name ); if( s2->last_name != NULL ) free( s2->last_name ); s2->grade = s1->grade; s2->id = s1->id; s2->last_name = (malloc((strlen(s1->last_name) + 1) * sizeof(char))); s2->first_name = (malloc((strlen(s1->first_name) + 1) * sizeof(char))); strncpy(s2-> first_name, s1->first_name, strlen(s1->first_name) + 1); strncpy(s2-> last_name, s1->last_name, strlen(s1->last_name) + 1); } 

Вместо этого:

 newStudentp -> last_name = (malloc((strlen(last_name) + 1) * sizeof(char))); 

делать:

 newStudentp -> last_name = strdup (last_name); 

Ваша глубокая копия хочет сделать что-то подобное (не совсем то, что предложил cnicutar):

 s2->first_name = strdup (s1->first_name); 

Проблема с предложением cnicutar заключается в том, что ему необходимо вручную выделить буфер перед strcpy.

И если я правильно помню:

* s2 = * s1;

сделают мелкую копию.

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