Как объявить структуры в C, чтобы избежать повреждения структуры?

У меня есть аналогичная проблема, такая как этот вопрос, и моя проблема была решена аналогично этот ответ, но я не могу понять, что вызвало эту проблему. У меня есть три структуры File, Line и Buffer, объявленные в app.h.

typedef struct File {
    FILE *fs;
    char *path;
    size_t size;
} File;

typedef struct Line Line;
struct Line {
    char *text;
    size_t len;
    size_t line_no;
    Line *next;
    Line *prev;
};

typedef struct Buffer {
    int id;
    File file;
    Line *first;
    Line *last;
    Line *current;
    int x_pos;
    int y_pos;
    int visual_x;
    bool modified;
} Buffer;

Makefile выглядит следующим образом:

CC = gcc
CFLAGS = -Wall -Werror -g
LDFLAGS = 
LDLIBS = -lcurses
OBJECTS = app.o io.o global.o move.o winio.o utils.o

all: app

app: $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o $@

%.o: %.c proto.h app.h
    $(CC) $(CFLAGS) -c $<

.PHONY: clean
clean:
    -rm app
    -rm *.o

Существует глобальный указатель для структуры Buffer: extern Buffer *buffer объявлено в proto.h и определено в global.c, а *buffer динамически размещено в io.c. В io.c есть несколько функций, которые работают с *buffer. Я переместил функцию show_buffer() модуля io.c в другой модуль, например winio.c.

void show_buffer()
{
    size_t i = 0, j = 0;
    Line *it;

    for (it = buffer->first; it != NULL && i < (LINES - STATBAR_HEIGHT);
            it = it->next, i++) {
        for (j = 0; it->text[j] != '\0'; j++) {
            waddch(mainwin, it->text[j]);
        }

    }
}

и я также удалил size член структуры File. Потом show_buffer() в winio.c уже не работало. Я обнаружил, что show_buffer() работает с поврежденным указателем buffer->first. Я перекомпилировал каждый модуль, но это не сработало.

Проблема была решена, когда я снова добавил size элемент структуры File. Я также понял, что если я верну show_buffer() обратно в io.c, проблема будет решена. Я подозреваю, что заполнение структуры может вызвать проблему.

Мой вопрос в том, что вызвало проблему и как ее избежать.

Разрешение

Предварительно скомпилированный заголовок вызывал проблемы, потому что он не перекомпилировался.


person ziggurat    schedule 17.02.2015    source источник
comment
Вы пытались полностью перекомпилировать (например, make clean && make в Linux)?   -  person rlbond    schedule 18.02.2015
comment
Вам действительно нужно опубликовать MCVE.   -  person M.M    schedule 18.02.2015
comment
Глобальные переменные — плохие новости даже в лучшие времена. Здесь много движущихся частей (минимум app.h, proto.h, gobal.c [sic], io.c, winio.c). Вы уверены, что правильно все перекомпилировали? Один из способов получить повреждение — перекомпилировать некоторые, но не все файлы. Вокруг много указателей; все ли они должным образом инициализированы перед использованием. Откровенно говоря, это достаточно запутанно, чтобы на него нельзя было ответить. Вам нужно будет показать MCVE (как упоминалось ранее), но это не будет тривиальным предложением.   -  person Jonathan Leffler    schedule 18.02.2015
comment
@rlbond Да, я перекомпилировал каждый модуль   -  person ziggurat    schedule 18.02.2015
comment
@JonathanLeffler Да, все указатели правильно инициализированы. Как я упоминал в своем посте, проблема решается либо перемещением show_buffer() обратно в исходный модуль, либо повторным добавлением size члена File структуры. Эта проблема воспроизводима.   -  person ziggurat    schedule 18.02.2015
comment
Это не вопрос объявления struct. Вероятно, у вас есть некоторые повреждения памяти. Используйте такие инструменты, как valgrind и gdb   -  person Basile Starynkevitch    schedule 18.02.2015
comment
Как отмечено в ответе, вам нужно проверить несколько определений структуры и убедиться, что вы включаете правильный заголовок в каждый файл c, который использует структуру. Боюсь, не видя всех файлов и не зная деталей того, как вы компилируете программу, вы не получите ничего, кроме совета о том, что нужно делать, или догадок.   -  person Retired Ninja    schedule 18.02.2015
comment
Нет; это не воспроизводится нами. У нас нет и, вероятно, не может быть всего вашего кода (его слишком много). То, что вы показали в вопросе, не позволяет нам воспроизвести проблему. Перемещение функции между исходными файлами не приводит к повреждению — происходит что-то еще, что вы еще не отследили. Вы уже использовали valgrind? Вы выполняете много связанных списков и другую работу, которая должна включать динамическое выделение памяти; valgrind укажет, злоупотребляете ли вы динамически выделенной памятью.   -  person Jonathan Leffler    schedule 18.02.2015
comment
@JonathanLeffler Я запустил программу с valgrind, и об ошибках не сообщалось.   -  person ziggurat    schedule 18.02.2015
comment
Мое любопытство раздирается. Если хотите, пришлите мне архив кода по электронной почте — см. мой профиль.   -  person Jonathan Leffler    schedule 18.02.2015
comment
@JonathanLeffler Когда я создавал запрошенный вами код архива, я нашел причину проблемы. вы можете проверить мой ответ на мой вопрос.   -  person ziggurat    schedule 18.02.2015


Ответы (2)


Определения структуры являются данными времени компиляции. Как только исходный файл скомпилирован, макеты struct, которые будут приняты в результирующих двоичных компонентах, фиксируются. Если вы не убедитесь, что все компоненты вашей системы, работающие с данным типом struct, имеют одинаковое представление о расположении этого типа (как описано ниже), это может легко привести к повреждению данных. Вам повезло, что в вашем случае порчу было так легко распознать.

Ваше определение struct должно появиться в заголовочном файле (только), и каждый исходный файл, который на него опирается, должен #include указывать этот заголовок. Если вы когда-нибудь измените структуру struct, добавив или удалив член, изменив тип члена или переупорядочив элементы, все исходные файлы, которые полагаются на заголовок, должны быть перекомпилированы. (Любая приличная система сборки поможет вам сделать это довольно легко.)

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

person John Bollinger    schedule 17.02.2015

Отвечаю на свой вопрос в надежде, что он будет полезен другим. Пока я исследовал причину проблемы, я наткнулся на предварительно скомпилированный заголовочный файл app.h.gch в рабочем каталоге. Я подозреваю, что в предыдущих версиях моего Makefile я передал app.h компилятору, не зная об этом. К сожалению, в Makefile не было правила удаления предварительно скомпилированных заголовков. Когда я удалил app.h.gch и перекомпилировал весь проект, все пришло в норму.

person ziggurat    schedule 18.02.2015
comment
Я рад, что вы решили это. Я бы не догадался, что причиной проблемы станут предварительно скомпилированные заголовки. Я пытался использовать их несколько лет назад и не очень успешно, а время компиляции не было достаточно мучительным, чтобы за предполагаемой выгодой стоило гоняться. - person Jonathan Leffler; 18.02.2015