【问题标题】:Segmentation fault in cast struct in cc中强制转换结构中的分段错误
【发布时间】:2018-06-12 16:45:21
【问题描述】:

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

在下面的代码中,我有一个 c-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;
}

【问题讨论】:

  • @UnholySheep 它实际上返回了取消引用的c。所以我不清楚整个带有指针的练习。可能是为了解决classclassSource 之间的不兼容问题。约翰,你为什么认为你可以退回一个而不是另一个?
  • 您的结构不兼容。也许您可以在第一个字段中逃脱,但其余部分则不行。
  • 您正在为函数指针分配嵌套函数,这是非标准 C(不允许嵌套函数)。所以我真的不知道它的行为,但可以想象这些可能表现为局部变量,不能从函数外部访问。
  • C 没有类:你一半使用 C,一半使用 C++。
  • 这里的一切都是未定义的行为。强制转换不能这样工作,嵌套函数不能这样工作(实际上标准 C 根本没有嵌套函数,你(错误)使用了非标准编译器扩展),没有闭包,你不能返回本地对象的地址。您可以在 C 中执行 OOP,但您无法修复您的代码。我会说你需要忘掉你学错的东西并重新开始。推荐你获取X Toolkit Intrinsics的源码,好好研究一下。

标签: c oop struct segmentation-fault


【解决方案1】:

您的代码中至少存在三个不同的问题。

  1. 您实际上并没有“其他结构相同但没有隐藏属性的结构”。你的 classclassSource 结构在不同的地方有它们的 getPVsetPV 成员。内部成员访问归结为结构开头的字节偏移量。为了有机会工作,您的代码需要在两种结构类型之间有一个共同的成员初始前缀(即去掉 int apv; 或将其移至末尾)。

  2. 您正在按值返回一个结构,它会自动生成一个副本。您已经重新实现了object slicing 问题:因为返回值的类型为class,所以只会复制class 的成员。 classSource 的额外成员已被“切掉”。

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

    如果你试图在包含函数退出后通过它的地址调用嵌套函数,那么一切都会崩溃。

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

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

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

    我有点确定将一个结构嵌入另一个结构是可以的,但是:

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

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

  2. 更改您的代码以改用指针。返回指向结构的指针可以避免切片问题,但现在您必须处理内存管理(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),并且您希望编写在所有这些类型上统一工作的代码,这主要是有用的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-21
    • 2011-08-24
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多