Создание глубокой копии структуры ... создание мелкой копии структуры - PullRequest
13 голосов
/ 02 августа 2011

Есть вопросы, подобные этому, но они недостаточно похожи на мой конкретный вопрос, на который я могу ответить.

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

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

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;
}

Для глубоких копий структур с указателями-членами я знаю, что мы должны сделать НАШУ функцию копирования, которая делает что-нибудь разумное с указателями. Что это за разумная вещь ... Я не уверен ... так вот моя попытка этой ГЛУБОКОЙ копии.

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;

}

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

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

Мелкая копия: тетср (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);
}

Ответы [ 6 ]

5 голосов
/ 02 августа 2011

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

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 имена в первую очередь (или же включают флаги «следует освободить», чтобы вам не приходилось копировать буквенные строки).

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

2 голосов
/ 02 августа 2011

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

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

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

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

1 голос
/ 23 июня 2018

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я предполагаю 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 (), которая выделяет память в куче ( heap это означает, что он останется даже после выхода из create_Student ().

Поместить амперсанд перед s1 или s2 - все равно что сказать: «Дай мне адрес адреса моей структуры Student»

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

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

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

Таким образом, используя ту же логику «необходимости делать копию того, на что указывают указатели», ваша копия DEEP может выглядеть следующим образом:

// 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); 
}
0 голосов
/ 02 августа 2011

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

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

do:

newStudentp -> last_name = strdup (last_name);

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

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

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

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

* s2 = * s1;

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

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

0 голосов
/ 02 августа 2011
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); 
}
0 голосов
/ 02 августа 2011

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

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...