关于oop:c中的强制转换结构中的分段错误

Segmentation fault in cast struct in c

为了封装结构成员(以与本问题讨论类似的方式),我创建了以下代码。

在下面的代码中,我有一个c结构,它包含一些方法来访问隐藏的结构成员(通过转换为结构,否则将其转换为其他结构,但没有隐藏的属性)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>

typedef struct class {
    int publicValue;
    int (*getPV)();
    void (*setPV)(int newPV);
} class;

typedef struct classSource {
    int publicValue;
    int apv;
    int (*getPV)();
    void (*setPV)(int newPV);
    int PV;
} classSource;

class class_init() {
    classSource cs;
    cs.publicValue = 15;
    cs.PV = 8;
    int class_getPV() {
        return cs.PV;
    };
    void class_setPV(int x) {
        cs.PV = x;
    };
    cs.getPV = class_getPV;
    cs.setPV = class_setPV;
    class *c = (class*)(&cs);
    return *c;
}


int main(int argc, const char * argv[]) {
    class c = class_init();
    c.setPV(3452);
    printf("%d", c.publicValue);
    printf("%d", c.getPV());
    return 0;
}

运行此命令时,出现分段错误错误。但是,我注意到,如果我注释掉某些代码行,它似乎可以正常工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>

typedef struct class {
    int publicValue;
    int (*getPV)();
    void (*setPV)(int newPV);
} class;

typedef struct classSource {
    int publicValue;
    int apv;
    int (*getPV)();
    void (*setPV)(int newPV);
    int PV;
} classSource;

class class_init() {
    classSource cs;
    cs.publicValue = 15;
    cs.PV = 8;
    int class_getPV() {
        return cs.PV;
    };
    void class_setPV(int x) {
        cs.PV = x;
    };
    cs.getPV = class_getPV;
    cs.setPV = class_setPV;
    class *c = (class*)(&cs);
    return *c;
}


int main(int argc, const char * argv[]) {
    class c = class_init();
    c.setPV(3452);
    //printf("%d", c.publicValue);
    printf("%d", c.getPV());
    return 0;
}

我认为这可能与使用初始化程序将getter和setter方法添加到结构中有关,因为这些方法可能会覆盖内存。

我正在做未定义的行为吗?有没有办法解决这个问题?

编辑:在以下答案的帮助下,我重新编写了代码。如果有人想看一下实现,下面是修改后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int pub;
} class;

typedef struct {
    class public;
    int PV;
} classSource;

int class_getPV(class *c) {
    return ((classSource*)c)->PV;
}

void class_setPV(class *c, int newPV) {
    ((classSource*)c)->PV = newPV;
}

class *class_init() {
    classSource *cs = malloc(sizeof(*cs));
    if((void*)cs == (void*)NULL) {
        printf("Error: malloc failed to allocate memory");
        exit(1);
    }
    cs->public.pub = 10;
    cs->PV = 8;
    return &(cs->public);
}

int main() {
    class *c = class_init();
    class_setPV(c,4524);
    printf("%d\
"
,class_getPV(c));
    printf("%d\
"
,c->pub);

    free(c);
    return 0;
}


您的代码中至少存在三个独立的问题。

  • 您实际上没有"结构,否则没有隐藏的属性"。您的classclassSource结构在不同位置具有其getPVsetPV成员。内部成员访问可归结为从结构开始处的字节偏移量。为了获得更大的工作机会,您的代码将需要在两种结构类型之间具有相同的成员初始前缀(即摆脱int apv;或将其移到末尾)。

  • 您正在按值返回结构,该结构会自动创建一个副本。您已经重新实现了对象切片问题:由于返回值的类型为class,因此将仅复制class的成员。 classSource的额外成员已被"切断"。

  • 您正在使用嵌套函数。这不是C的标准功能。 GCC将其实现为扩展,并说:

    If you try to call the nested function through its address after the containing function exits, all hell breaks loose.

    这正是代码中发生的事情:class_init返回后,您正在调用c.setPV(3452);c.getPV

  • 如果要解决这些问题,则必须:

  • 修复您的结构定义。至少class的所有成员都必须以相同的顺序出现在classSource的开头。即使您这样做了,我也不确定您是否仍然会遇到未定义的行为(例如,您可能违反了别名规则)。

    我可以肯定将一个结构嵌入另一个结构中是可以的,但是:

    1
    2
    3
    4
    typedef struct classSource {
        class public;
        int PV;
    } classSource;

    现在,您可以从初始化程序中返回&cs->public,并且您的方法可以将class *指针转换回classSource *。 (我认为这是可以的,因为所有结构指针都具有相同的大小/表示形式,并且保证与第一个成员相同的X.public具有与X相同的内存地址。)

  • 更改代码以改为使用指针。返回指向结构的指针可以避免切片问题,但是现在您必须注意内存管理(malloc结构,以后要注意free)。

  • 不要使用嵌套函数。而是将指向该对象的指针传递给每个方法:

    1
    2
    3
    class *c = class_init();
    c->setPV(c, 3452);
    int x = c->getPV(c);

    这有点乏味,但这就是例如C本质上是在后台执行的。除了C不会将函数指针放在对象本身之外;没有理由何时可以使用常规功能:

    1
    2
    setPV(c, 3452);
    int x = getPV(c);

    ...或使用单独的(全局,常量,单例)结构仅存储指向方法的指针(不包含数据)。然后,每个对象仅包含一个指向此方法结构的指针(称为vtable):

    1
    2
    3
    4
    5
    6
    7
    8
    struct classInterface {
        void (*setPV)(class *, int);
        int (*getPV)(const class *);
    };
    static const classInterface classSourceVtable = {
        class_setPV,  // these are normal functions, defined elsewhere
        class_getPV
    };

    方法调用如下所示:

    1
    2
    c->vtable->setPV(c, 1234);
    int x = c->vtable->getPV(c);

    但是,如果您有几种不同的结构类型共享一个公共的公共接口(class),并且想要编写对所有这些结构都可以统一使用的代码,则这主要有用。