C Путаница с мелкой копией
Редактировать: Исправлена ошибка, которая упоминалась в комментариях, и проблема все еще существует, так что этот вопрос на самом деле не является дубликатом - только то, что мои навыки программирования на C не очень хороши на данный момент, решение, приведенное ниже, отвечает на вопрос и не 't исправить ошибку локальной переменной.
Предупреждение: довольно новый для C
Я понимаю, что когда мы присваиваем структуру другой структуре, выполняется поверхностное копирование. Однако я не смог понять результат того, почему это произошло:
Предположим, что следующее, где я пытаюсь инициализировать структуру, назовите ее структурой Type2, членом Type1 с помощью оператора присваивания, тогда должна была быть выполнена мелкая копия. Это означает, что адрес члена Type2 скопирован:
typedef struct {
uint8_t someVal;
} Type1
typedef struct {
Type1 grid[3][3];
} Type2
//Constructor for Type2 "objects"
Type2 Type2_Constructor(void) {
Type1 empty = {.value = o}
Type2 newType2;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
//Shallow copy empty struct
newType2.grid[i][j] = empty;
}
return newType2;
}
int main (void) {
Type2 type2Object = Type2_Constructor();
for (int i = 0; i < 3; i ++)
for (int j = 0; j < 3; j++){
printf("%u",type2OBject.grid[i][j].value);
printf("\n\r%p\n\r",&(type2Object.grid[i][j].value));
printf("\n\r");
}
return 0;
}
Я ожидаю увидеть:
0
0xMemoryLocation
0
0xMemoryLocation
0
0xMemoryLocation
.
.
.
На самом деле, я вижу что-то вроде этого, где адрес увеличивается на 2 байта:
0
0x7fff57eaca18
0
0x7fff57eaca1a
0
0x7fff57eaca1c
0
0x7fff57eaca1e
0
0x7fff57eaca20
.
.
.
Так как мелкое копирование должно копировать адрес напрямую, почему &(type2Object.grid[i][j].value) отличаются?
Спасибо за помощь!
3 ответа
Подумайте об указателях. Если у вас есть две переменные-указатели одного типа, давайте вызовем их a
а также b
, Если вы делаете a = b
вы делаете мелкую копию, вы копируете только фактический указатель b
в a
а не память, которая b
указывает на. Это означает, что оба a
а также b
указывает на ту же память.
Глубокая копия будет копировать содержимое того, что b
указывает на, в какой-то недавно выделенной памяти. Глубокая копия приведет к a
а также b
указать на другую память.
Принимая вашу структуру:
typedef struct {
Type1 grid[3][3];
} Type2
Если у вас есть
Type2 a;
Type2 b = { ... }; // Some initialization, not relevant exactly what
затем назначение
a = b; // Copy structure b to a
Это мелкая копия. Но поскольку данные структуры являются массивом, весь массив копируется и выглядит как глубокая копия.
Если бы внутри структуры был указатель, был бы скопирован только указатель. Другой пример с новой структурой:
typedef struct {
char *pointer;
} Type3;
Type3 a;
Type3 b = { "abcd" };
a = b;
printf("a.pointer = %s\n", a.pointer);
printf("b.pointer = %s\n", b.pointer);
Приведенный выше код будет печатать одну и ту же строку для обоих a.pointer
а также b.pointer
, Но это тот же указатель. Если мы добавим новую распечатку указателей:
printf("a.pointer = %p\n", (void *) a.pointer);
printf("b.pointer = %p\n", (void *) b.pointer);
Две вышеупомянутые строки после присвоения выдают одно и то же значение. Оба указателя будут указывать на одну и ту же память. Это мелкая копия.
Кроме того, структурное назначение, подобное приведенным выше, ничем не отличается от
memcpy(&a, &b, sizeof a);
На самом деле, я думаю, что ваша путаница заключается в том, что вы думаете, что ссылки на другую структуру похожи на ссылки в Java, C# или C++, а это не так.
Каждый элемент в grid
массив в Type2
структура является уникальным примером Type1
состав. И как уникальные экземпляры, они все занимают разную память, что приводит к тому, что вы печатаете адреса, все разные.
Когда вы делаете
newType2.grid[i][j] = empty;
вы копируете содержимое empty
экземпляр структуры, в экземпляр структуры newType2.grid[i][j]
, С использованием memcpy
вызов, показанный выше, что назначение действительно
memcpy(&newType2.grid[i][j], &empty, sizeof newType2.grid[i][j]);
Это делает побитовую копию содержимого empty
,
О разнице между указателем и массивом рассмотрим следующие определения:
int a[] = { 1, 2, 3, 4 };
int *p = a;
В памяти это будет выглядеть примерно так:
+ --- + --- + --- + --- + | 1 | 2 | 3 | 4 | +---+ --- + --- + --- + ^ | +---+ | р | +---+
То есть массив и его содержимое занимают достаточно места для четырех int
ценности. Тогда у вас есть указатель p
который указывает на первый элемент в массиве (массивы распадаются на указатели на их первый элемент, в случае массива a
выше, используя a
как указатель равен &a[0]
).
Вы, кажется, неправильно понимаете, что такое мелкая / глубокая копия. Мелкая копия будет копировать каждого члена (или делать побитовую копию, если хотите). Глубокая копия также будет копировать собственные ресурсы структуры.
Давайте начнем с простого примера:
struct X {
int val;
};
X x1 = {24};
X x2 = {42};
x1 = x2;
Да, оператор встроенного оператора присваивания =
выполняет мелкую копию. Это означает, что вышеизложенное эквивалентно:
x1.val = x2.val;
В этом случае как X
не имеет внешних ресурсов, которыми он владеет, поэтому он также считается глубокой копией, потому что каждый x2
собственные копируется в x1
, Это является следствием того факта, что X
не владеет никакими внешними ресурсами.
Важность мелкой / глубокой копии вступает в игру, когда X
будет владеть внешним ресурсом / Например:
struct X {
int *my_very_own_external_value;
};
X x1, x2;
x1.my_very_own_external_value = malloc(sizeof(int));
*x1.my_very_own_external_value = 24;
x2.my_very_own_external_value = malloc(sizeof(int));
*x2.my_very_own_external_value = 42;
Мелкая копия будет такой:
x1 = x2;
Это было бы эквивалентно этому:
x1.my_very_own_external_value = x2.my_very_own_external_value;
Что, если вы понимаете указатели, означает, что x1
потерял адрес выделенной памяти (кто держит 24
) и теперь оба x1
а также x2
иметь указатели на память, выделенную для x2
то есть тот, который хранит 42
;
Если вам нужна глубокая копия, вам нужно написать:
free(x1.my_very_own_external_value);
x1.my_very_own_external_value = malloc(sizeof(int));
*x1.my_very_own_external_value = *x2.my_very_own_external_value;
Теперь вы скопировали память (внешний ресурс), которая x2
владеет внешне.
отказ от ответственности: я мог бы написать в стиле C++
В вашем коде есть ошибка:
//Constructor for Type2 "objects"
Type2 * Type2_Constructor(void) {
Type1 empty = ...;
Type2 newType2;
... do something ...
Type2 * newType2Ptr = &newType2;
return newType2Ptr;
}
newType2 является локальной переменной - структурой, которая определена в стеке. Затем вы возвращаете его адрес вызывающей функции. Как только вы выйдете из функции Type2_Constructor, ее стек больше не будет в ваших руках.
Когда вы печатаете значения из этой области, вы получаете все что угодно (вы надеетесь получить дамп ядра, но, к сожалению, это не всегда так).