El getc de ANSI C causa segfault en Linux pero no en OS X
-
06-07-2019 - |
Pregunta
Tengo un código ANSI C que desarrollé en mi Mac, pero cuando traté de ejecutarlo en los servidores Linux de nuestra escuela me da un defecto.
La línea específica que me está causando problemas es un getc
de un puntero de archivo.
El archivo existe.
Aquí está el método en cuestión:
// 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;
}
La llamada a init_intlists
que causa la segfault se inicia así:
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);
y parse_args
se ve así:
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);
}
Entonces, cuando intento invocar esto en mi Mac, funciona perfectamente y lee el archivo como debería y puedo operarlo y obtener las respuestas correctas para mi tarea.
Sin embargo, cuando intento ejecutarlo en Linux, obtengo un defecto cuando intenta getc
en la subrutina init_intlists
.
He verificado que los archivos que proporciono para la entrada existen y son legibles en todo el mundo (umask 755). He intentado con rutas absolutas y relativas. También he probado varios archivos de entrada diferentes.
He intentado usar gcc 4.2
y gcc 3.4
en el servidor Linux y ambos producen un ejecutable binario que causará una falla por defecto con cualquier archivo de entrada dado.
Aquí está la información de la versión entre las dos versiones diferentes de 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)
Linux:
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)
Invoco el compilador usando el mismo Makefile
tanto en OS X como en Linux. La invocación final de gcc
termina así:
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
¿Alguna idea? Estoy completamente perdido, al igual que mi profesor.
Solución
Las otras respuestas son correctas: trate un FILE *
como un asa opaca que copie, no intente copiar su contenido. Específicamente, puede corregir su código de la siguiente manera:
Elimine la llamada a malloc
cuando inicialice fp
y base_vector_fp
:
FILE *fp = NULL;
FILE *base_vector_fp = NULL;
Pase un puntero a estos punteros a parse_args
, para que pueda actualizar los valores del puntero:
parse_args(argc, argv, &fp, &base_vector_fp);
Y cambie parse_args
para actualizar los objetos FILE *
en la persona que llama, en lugar de intentar trabajar con los objetos 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);
}
Otros consejos
Se supone que no debe asignar sus propios objetos FILE
, generalmente son objetos opacos administrados por libc. No los free ()
tampoco, eso lo hace fclose (3)
. Aunque en teoría podría asignar uno y hacer una asignación de estructura y hacer que funcione, sería mejor no luchar contra la biblioteca y simplemente pasar la referencia como lo hacen todos los demás. La biblioteca puede o no mantener un estado que no está en la estructura de ARCHIVO, y mirar dentro o desreferenciar la estructura completa es un estilo suficientemente malo que los implementadores pueden asumir que nunca lo haces.
Si desea devolver un FILE *
, puede usarlo como un valor de puntero de retorno como lo hizo en un caso o usar un puntero doble indirecto: FILE * fp; f (& amp; fp);
.
Hmm, acabo de notar que C99 realmente especifica esto en 7.19.13 :
6 La dirección del objeto FILE utilizado para controlar una corriente puede ser significativo; una copia de un objeto FILE no necesita servir en lugar del original.
Con esto, están notando que un FILE *
puede ser realmente una galleta mágica.
No debe copiar el resultado de fopen ()
en un objeto FILE
, y de hecho, no debe malloc
a FILE
en absoluto. Siempre debe usar fopen ()
para asignar el objeto de control FILE
.
El objeto FILE
es opaco, y en verdad contiene mucho que está oculto a los simples mortales. La implementación es gratuita para incluir todo tipo de cosas, como punteros a otras estructuras de control, etc.