背景:相互兼容的结构体间(最普通的就是同一类型的结构体)简单赋值(就是“=”),以C9x的标准,是可以直接直接赋值的
情境1
假设有两个一样的结构体
typedef struct SS {
int a;
int b;
} SSS;
SSS *s1, *s2;
s1 = malloc (sizeof(struct SS));
s1->a = 23;
s1->b = 24;
s2 = malloc (sizeof(struct SS));
s2 = s1;
free(s1);
printf ("%d %d", s2->a, s2->b);
s2 = s1
这样赋值的话,语句printf ("%d %d", s2->a, s2->b);
会导致访问已经free的内存吗?
情境2
typedef struct A {
int a;
int b;
} AA;
typedef struct B {
int c;
struct A stra;
} BB;
struct B *new_b1, *new_b2;
new_b1 = malloc(sizeof(struct B));
new_b2 = malloc(sizeof(struct B));
new_b1->c = 23;
new_b1->stra.a = 24;
new_b1->stra.b = 25;
new_b2->c = 26;
new_b2->stra = new_b1->stra;
free(new_b1);
printf ("%d %d", new_b2->stra.a, new_b2->stra.b); //如果这样,printf访问的是已经free的空间吗?
结构体赋值和指针赋值是两个不同的概念。在你的例子里,s1, s2 都是指针,那么 s2 = s1是指针赋值,赋值之后s2,s1就之指向同样的内存地址,所以会导致访问已经free的内存。结构体赋值是会把各个member的值copy的。
补充一下:
我们可以看看编译器是怎么实现结构体赋值的。比如:
#include <stdio.h>
typedef struct A {
int x;
int y;
} A;
int main() {
A a = {1, 2};
A aa = a;
printf("aa.x = %d\n", aa.x);
}
下面是clang生成的llvm IR.
%struct.A = type { i32, i32 }
@main.a = private unnamed_addr constant %struct.A { i32 1, i32 2 }, align 4
@.str = private unnamed_addr constant [11 x i8] c"aa.x = %d\0A\00", align 1
; Function Attrs: nounwind uwtable
define i32 @main() #0 {
%a = alloca %struct.A, align 4
%aa = alloca %struct.A, align 4
%1 = bitcast %struct.A* %a to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %1, i8* bitcast (%struct.A* @main.a to i8*), i64 8, i32 4, i1 false)
%2 = bitcast %struct.A* %aa to i8*
%3 = bitcast %struct.A* %a to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %2, i8* %3, i64 8, i32 4, i1 false)
%4 = getelementptr inbounds %struct.A, %struct.A* %aa, i32 0, i32 0
%5 = load i32, i32* %4, align 4
%6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str, i32 0, i32 0), i32 %5)
ret i32 0
}
这里有两个llvm.memcpy
, 第一个是给a
赋值,第二个是给aa
赋值。所以C编译器仅仅是做了一个memcpy
,把a
的内容拷贝到aa
所在的内存。所以相互兼容的结构体间可以这样赋值,但是这样做有没有意义就要看具体情况了吧。
你说的第二种情景,其实就是结构体赋值,因为new_b2->stra
不是指针。所以不会访问free掉的内存。
下面的例子更可以说明问题:
typedef struct A {
int x;
int y;
} A;
int main() {
A a1 = {1, 2};
A a2 = a1;
A *s1 = (A *) malloc(sizeof(A));
A *s2 = (A *) malloc(sizeof(A));
*s1 = a1;
*s2 = *s1;
printf("s2->x = %d\n", s2->x);
s2 = s1;
free(s1);
printf("s2->x = %d\n", s2->x);
}
再看*s2 = *s1
和 s2 = s1
LLVM代码:
%11 = load %struct.A*, %struct.A** %s2, align 8
%12 = load %struct.A*, %struct.A** %s1, align 8
%13 = bitcast %struct.A* %11 to i8*
%14 = bitcast %struct.A* %12 to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %13, i8* %14, i64 8, i32 4, i1 false)
%15 = load %struct.A*, %struct.A** %s2, align 8
%16 = getelementptr inbounds %struct.A, %struct.A* %15, i32 0, i32 0
%17 = load i32, i32* %16, align 4
%18 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str, i32 0, i32 0), i32 %17)
%19 = load %struct.A*, %struct.A** %s1, align 8
store %struct.A* %19, %struct.A** %s2, align 8
%20 = load %struct.A*, %struct.A** %s1, align 8
%21 = bitcast %struct.A* %20 to i8*
call void @free(i8* %21) #1
%22 = load %struct.A*, %struct.A** %s2, align 8
%23 = getelementptr inbounds %struct.A, %struct.A* %22, i32 0, i32 0
%24 = load i32, i32* %23, align 4
%25 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str, i32 0, i32 0), i32 %24)
*s2 = *s1
赋值的时候,LLVM是把s1
指向的内容memcpy
到s2
指向的内存。这里LLVM要先derefence这两个指针。而s2 = s1
赋值的时候不用memcpy
而是直接把s1的内容store到s2.
希望这个例子能说明这两种赋值的区别。
这里有个讨论帖可以看一下
http://bbs.csdn.net/topics/110160458