c中的cast结构中的分段错误

时间:2018-06-12 16:45:21

标签: c oop struct segmentation-fault

在尝试封装struct成员时(以this问题中讨论的方式),我创建了下面的代码。

在下面的代码中,我有一个c-struct,它包含访问隐藏的struct的成员的方法(通过强制转换为结构,否则相同但没有隐藏的属性)

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

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

#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方法添加到结构中有关,因为这些可能会覆盖内存。

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


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

#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\n",class_getPV(c));
    printf("%d\n",c->pub);

    free(c);
    return 0;
}

1 个答案:

答案 0 :(得分:1)

您的代码中至少有三个单独的问题。

  1. 您实际上没有&#34; 结构,否则相同但没有隐藏属性&#34;。您的classclassSource结构使其getPVsetPV成员位于不同的位置。内部成员访问可归结为结构开头的字节偏移量。为了有机会工作,你的代码需要在两个结构类型之间有一个共同的成员初始前缀(即摆脱int apv;或将它移到最后)。

  2. 您按值返回结构,自动生成副本。您已重新实现object slicing问题:由于返回值的类型为class,因此只会复制class的成员。 classSource的额外成员已被&#34;切掉&#34;。

  3. 您正在使用嵌套函数。这不是C的标准功能; GCC implements it as an extension并说:

      

    如果你尝试在包含函数退出后通过其地址调用嵌套函数,那么所有的地狱都会崩溃。

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

  4. 如果您想解决这些问题,您必须:

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

      我有点确定将一个结构体嵌入另一个结构中就可以了,但是:

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

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

    2. 将代码更改为使用指针。返回指向struct的指针可以避免切片问题,但现在你必须处理内存管理(malloc结构并稍后注意free

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

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

      这有点单调乏味,但这就是例如从本质上讲,C ++本身也是如此。除了C ++之外,没有将函数指针放在对象本身中;没有理由你什么时候可以使用正常功能:

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

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

      struct classInterface {
          void (*setPV)(class *, int);
          int (*getPV)(const class *);
      };
      static const classInterface classSourceVtable = {
          class_setPV,  // these are normal functions, defined elsewhere
          class_getPV
      };
      

      方法调用如下所示:

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

      但是,如果你有几个不同的结构类型共享一个公共公共接口(class)并且你想编写在所有结构上统一工作的代码,那么这主要是有用的。