Ошибка сегментации в структуре приведения в c - PullRequest
0 голосов
/ 12 июня 2018

В попытке инкапсулировать члены структуры (аналогично тому, как обсуждалось в этом вопросе), я создал код ниже.

В приведенном ниже коде у меня есть 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;
}

Я предполагаю, что это может иметь какое-то отношение к использованию инициализатора для добавления метода получения и установкиметоды структуры, так как они могут перезаписать память.

Что я делаю, неопределенное поведение?Есть ли способ исправить это?

РЕДАКТИРОВАТЬ: С помощью ответа ниже, я переписал код.В случае, если кто-то хочет увидеть реализацию, ниже приведен пересмотренный код

#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 голосов
/ 12 июня 2018

В вашем коде есть как минимум три отдельные проблемы.

  1. На самом деле у вас нет структуры ", в остальном такая же, но без скрытых свойств ",Ваши структуры class и classSource имеют своих членов getPV и setPV в разных местах.Внутренний доступ к элементу сводится к смещению байтов с начала структуры.Чтобы иметь боевые шансы на работу, ваш код должен иметь общий начальный префикс членов между двумя типами структуры (то есть избавиться от int apv; или переместить его в конец).

  2. Вы возвращаете структуру по значению, которая автоматически создает копию.Вы переопределили проблему нарезки объектов : поскольку возвращаемое значение имеет тип class, будут скопированы только члены class.Дополнительные члены classSource были "вырезаны".

  3. Вы используете вложенные функции.Это не стандартная особенность C; GCC реализует его как расширение и говорит:

    Если вы попытаетесь вызвать вложенную функцию через ее адрес после выхода из содержащей функции, весь ад прекратит свое существование.

    Это именно то, что происходит в вашем коде: вы звоните c.setPV(3452); и c.getPV после возвращения class_init.

Если вы хотите исправить этопроблемы, вам нужно:

  1. Исправить ваши определения структуры.Как минимум, все члены class должны появляться в начале classSource в том же порядке.Даже если вы сделаете это, я не уверен, что вы все равно не столкнетесь с неопределенным поведением (например, вы можете нарушать правило псевдонимов).

    Я несколько уверен, что встраивание одной структуры в другуюбудьте в порядке, однако:

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

    Теперь вы можете вернуть &cs->public из инициализатора, и ваши методы могут привести указатель class * обратно к classSource *.(Я думаю, что это нормально, потому что все указатели структуры имеют одинаковый размер / представление, и X.public как первый член гарантированно будет иметь тот же адрес памяти, что и X.)

  2. Измените свой код, чтобы использовать вместо него указатели.Возврат указателя на структуру позволяет избежать проблемы среза, но теперь вам нужно позаботиться об управлении памятью (malloc структура и позаботиться о free позже).

  3. Не используйте вложенные функции.Вместо этого передайте указатель на объект каждому методу:

    class *c = class_init();
    c->setPV(c, 3452);
    int x = c->getPV(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), и вы хотите написать код, который одинаково работает на всех них.

...