ANSI C getc вызывает ошибку segfault в Linux, но не в OS X
-
06-07-2019 - |
Вопрос
У меня есть код ANSI C, который я разработал на своем Mac, но когда я попытался запустить его на Linux-серверах нашей школы, я получил ошибку segfault.
Конкретная строка, которая вызывает у меня проблемы, - это getc
из указателя файла.
Файл существует.
Вот метод, о котором идет речь:
// inits lists with all data in fp file pointer
// returns # of lines read
int init_intlists(FILE *fp, INTLIST *lists[]) {
int c, ctr;
ctr = 0;
// need to use a linked list to store current number
// for non 1-digit numbers...
INTLIST *cur_num = NULL;
int cur_num_len = 0;
while ((c = getc(fp)) != EOF){
if(c != '\n' && c != ' '){
c = c - 48;
if(cur_num == NULL){
cur_num = init_intlist(c);
} else {
list_append(cur_num, &c);
}
cur_num_len++;
} else if(c == ' ' || c == '\n'){
// we reached a space, meaning we finished
// reading a contiguous block of digits
// now we need to figure out what we actually read...
int num = 0;
INTLIST *ptr;
ptr = cur_num;
while(cur_num_len != 0){
cur_num_len--;
num += pow(10, cur_num_len) * ptr->datum;
ptr = ptr->next;
}
if(lists[ctr] == NULL){
// init new list
lists[ctr] = init_intlist(num);
} else {
// append to existing
list_append(lists[ctr], &num);
}
// clear cur_num to read the next one
cur_num_len = 0;
list_delete(cur_num);
cur_num = NULL;
}
if(c == '\n') {
// newline reached - increment to fill in next list
ctr++;
}
}
return ctr;
}
Звонок в init_intlists
это приводит к тому, что segfault начинается следующим образом:
FILE *fp = (FILE *)malloc(sizeof(FILE));
FILE *base_vector_fp = (FILE *)malloc(sizeof(FILE));
parse_args(argc, argv, fp, base_vector_fp);
if(fp == NULL || base_vector_fp == NULL){
fprintf(stderr, "Critical error, could not load input files\n");
return 1;
}
INTLIST *lines[MAX_LINES] = {};
INTLIST *base_vectors[MAX_LINES] = {};
int lines_read = init_intlists(fp, lines);
и parse_args
выглядит как:
FILE *load_file(char *filename) {
FILE *fp;
fp = fopen(filename, "r");
if(fp == NULL){
fprintf(stderr, "File %s does not seem to exist.\n", filename);
return NULL;
}
// XXX Does this memory leak?
// fp is never fclose()'d
return fp;
}
void parse_args(int argc, char *argv[], FILE *fp, FILE *base_vector_fp) {
char *prog = argv[0];
if (argc != 3){
fprintf(stderr, "Wrong number of arguments supplied.\nUse: %s <data_filename> <base_vector_filename>\n", prog);
free(fp);
free(base_vector_fp);
fp = NULL;
base_vector_fp = NULL;
exit(1);
}
char *filename = argv[1];
*fp = *load_file(filename);
char *base_vector_filename = argv[2];
*base_vector_fp = *load_file(base_vector_filename);
}
Поэтому, когда я пытаюсь вызвать это на своем Mac, он работает отлично, читает файл так, как должен, и я могу работать с ним и получать правильные ответы для своего задания.
Однако, когда я пытаюсь запустить его в Linux, я получаю ошибку при попытке getc
в init_intlists
подпрограмма.
Я проверил, что файлы, которые я предоставляю для ввода, существуют и доступны для чтения всем (umask 755).Я пробовал использовать как абсолютные, так и относительные пути.Я также попробовал несколько разных входных файлов.
Я пробовал использовать gcc 4.2
и gcc 3.4
на сервере Linux, и оба создают двоичный исполняемый файл, который вызовет ошибку сегментации для любых заданных входных файлов.
Вот информация о версии между двумя разными версиями gcc:
Mac OS X:
me@dinosaurhunter ~> gcc -v
Using built-in specs.
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5465~16/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5465)
Линукс:
me@janus:~/assignment_1$ gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --enable-languages=c,c++,fortran,objc,obj-c++,treelang --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.2 --program-suffix=-4.2 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
Я вызываю компилятор, используя тот же Makefile
как на OS X, так и на Linux.Конечный вызов gcc
в итоге выглядит так:
gcc -Wall -g -c src/common_file_io.c src/main.c src/intlist.c
gcc -Wall -g common_file_io.o main.o intlist.o -lreadline -lm -o bin/myprogram
Есть идеи?Я в полной растерянности, как и мой профессор.
Решение
Остальные ответы верны – лечите FILE *
в качестве непрозрачного дескриптора, который вы копируете, не пытайтесь скопировать его содержимое.В частности, вы можете исправить свой код следующим образом:
Удалить вызов на malloc
когда вы инициализируете fp
и base_vector_fp
:
FILE *fp = NULL;
FILE *base_vector_fp = NULL;
Передайте указатель на эти указатели parse_args
, чтобы он мог обновлять значения указателя:
parse_args(argc, argv, &fp, &base_vector_fp);
И изменить parse_args
обновить FILE *
объекты в вызывающем объекте, а не пытаться работать с FILE
объекты:
void parse_args(int argc, char *argv[], FILE **fp, FILE **base_vector_fp) {
char *prog = argv[0];
if (argc != 3){
fprintf(stderr, "Wrong number of arguments supplied.\nUse: %s <data_filename> <base_vector_filename>\n", prog);
exit(1);
}
char *filename = argv[1];
*fp = load_file(filename);
char *base_vector_filename = argv[2];
*base_vector_fp = load_file(base_vector_filename);
}
Другие советы
Вы не должны выделять свои собственные FILE
объекты, обычно это непрозрачные объекты, управляемые libc.Не free()
они тоже, это сделал fclose(3)
.Хотя теоретически вы могли бы выделить одну, назначить структуру и заставить ее работать, было бы лучше не бороться с библиотекой и просто передавать ссылку, как это делают все остальные.Библиотека может сохранять или не сохранять состояние, которого нет в структуре FILE, а заглядывать внутрь или разыменовывать всю структуру — достаточно плохой стиль, поэтому разработчики могут предположить, что вы никогда этого не сделаете.
Если вы хотите вернуть FILE *
вы можете либо использовать его как значение указателя возврата, как вы это сделали в одном случае, либо использовать двойной косвенный указатель: FILE *fp; f(&fp);
.
Хм, я только что заметил, что C99 действительно указывает это в 7.19.13:
6 Адрес объекта файла, используемый для управления потоком, может быть значительным;Копия объекта файла не должна служить вместо оригинала.
Этим они предупреждают, что FILE *
на самом деле это может быть просто волшебное печенье.
Не следует копировать результатfopen()
вFILE
возражать, и на самом деле вам не следуетmalloc
аFILE
объект вообще.Вы всегда должны использоватьfopen()
выделитьFILE
объект управления.
АFILE
объект непрозрачен, и воистину в нем заключено многое из того, что скрыто от простых смертных.Реализация может свободно помещать в нее все что угодно, например указатели на другие структуры управления и т. д.