首页 > C语言 结构体复制后free的问题

C语言 结构体复制后free的问题

背景:相互兼容的结构体间(最普通的就是同一类型的结构体)简单赋值(就是“=”),以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 = *s1s2 = s1LLVM代码:

  %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指向的内容memcpys2指向的内存。这里LLVM要先derefence这两个指针。而s2 = s1赋值的时候不用memcpy而是直接把s1的内容store到s2.

希望这个例子能说明这两种赋值的区别。


这里有个讨论帖可以看一下
http://bbs.csdn.net/topics/110160458

【热门文章】
【热门文章】