Когда я запускаю свой код, я получаю Segfault. Запуск valgrind показывает проблему.
==75112== Invalid read of size 8
==75112== at 0x1001D0C5F: flockfile (in /usr/lib/system/libsystem_c.dylib)
==75112== by 0x1001D303C: fscanf (in /usr/lib/system/libsystem_c.dylib)
==75112== by 0x100000D42: main (test.c:21)
==75112== Address 0x68 is not stack'd, malloc'd or (recently) free'd
==75112==
==75112==
==75112== Process terminating with default action of signal 11 (SIGSEGV)
==75112== Access not within mapped region at address 0x68
==75112== at 0x1001D0C5F: flockfile (in /usr/lib/system/libsystem_c.dylib)
==75112== by 0x1001D303C: fscanf (in /usr/lib/system/libsystem_c.dylib)
==75112== by 0x100000D42: main (test.c:21)
fscanf
был принят фиктивный дескриптор, потому что fopen
не удалось. В этом случае у меня не было input.txt
. Всегда проверяйте результат fopen
. Я использую функцию обертки, чтобы это всегда происходило.
#include <errno.h>
#include <string.h>
FILE *open_file(const char *filename, const char *mode) {
FILE *fp = fopen(filename, mode);
if(fp == NULL) {
fprintf(stderr, "Could not open %s: %s.\n", filename, strerror(errno));
exit(errno);
}
return fp;
}
Когда я заменить fopen
с open_file
я получаю сообщение об ошибке хорошего.
$ ./test
Could not open input.txt: No such file or directory.
Ok, touch input.txt
и запустить снова. Еще один секрет. Valgrind снова ...
==75478== Conditional jump or move depends on uninitialised value(s)
==75478== at 0x100000D0F: main (test.c:32)
==75478==
==75478== Conditional jump or move depends on uninitialised value(s)
==75478== at 0x100000D91: main (test.c:36)
==75478==
==75478== Conditional jump or move depends on uninitialised value(s)
==75478== at 0x100000DB9: main (test.c:37)
Первые два около n
, его неинициализированным, потому что input.txt пуст. fscanf
, который должен был заполнить его, не работает, поэтому n
содержит все, что он начал с мусора. Цикл выполняется случайным числом раз на основе мусора в n
. Он пытается и не удается прочитать из input.txt, поэтому p
остается неинициализированным, а также содержит мусор.
Третья ошибка заключается в попытке прочитать этот мусор в p
. n
содержит мусор, поэтому цикл for работает, пытаясь прочитать из неинициализированного массива p
.
Все это может быть исправлено путем инициализации n
и, как и прежде, проверки работы fscanf
. Опять же, функция обертки обеспечивает проверку ошибки.
int scan_check(FILE *restrict stream, const char *restrict format, ...) {
va_list args;
va_start(args, format);
int ret = vfscanf(stream, format, args);
if(ret <= 0) {
fprintf(stderr, "Could not match input to '%s'.\n", format);
exit(errno);
}
return ret;
}
Теперь он работает до конца. Все хорошо, правда? Нет, valgrind
говорит снова.
==76285== Syscall param write(buf) points to uninitialised byte(s)
==76285== at 0x1002DE97A: write$NOCANCEL (in /usr/lib/system/libsystem_kernel.dylib)
==76285== by 0x1001D8844: _swrite (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x1001D12FE: __sflush (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x1001D1001: fclose (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x100000E6E: main (test.c:60)
==76285== Address 0x100806bc4 is 4 bytes inside a block of size 4,096 alloc'd
==76285== at 0x100008EBB: malloc (vg_replace_malloc.c:303)
==76285== by 0x1001D468E: __smakebuf (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x1001E91DF: __swsetup (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x1001D3928: __sfvwrite (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x1001D3F02: fwrite (in /usr/lib/system/libsystem_c.dylib)
==76285== by 0x100000DDB: main (test.c:54)
Это говорит нам, что есть что-то не так с fwrite
вызовов.
fwrite(p[i].name,sizeof(PERSON),n,fout);
fwrite(p[i].tel,sizeof(PERSON),n,fout);
Это говорит о том, чтобы написать n
элементов каждого размера PERSON
структуры. Это было бы хорошо, если бы вы писали все p
. Но вы пишете только одно имя и один номер телефона. Вместо этого вы хотите записать символы, равные длине строки, плюс один для нулевого байта.
fwrite(p[i].name, sizeof(char), strlen(p[i].name)+1, fout);
fwrite(p[i].tel, sizeof(char), strlen(p[i].tel)+1 , fout);
Наконец, положить все это вместе, Valgrind не имеет никаких жалоб.
int main()
{
int n = 0;
PERSON p[50];
FILE *fin = open_file("input.txt","rt");
FILE *fout = open_file("output.dat","wb");
scan_check(fin,"%i", &n);
for (int i=0; i<n; ++i)
{
scan_check(fin,"%s%s", p[i].name, p[i].tel);
}
for (int i=0; i<n; ++i){
if (p[i].tel[1]=='3'){
fwrite(p[i].name, sizeof(char), strlen(p[i].name)+1, fout);
fwrite(p[i].tel, sizeof(char), strlen(p[i].tel)+1 , fout);
}
}
fclose(fin);
fclose(fout);
return 0;
}
Этот код можно сделать еще более безопасным путем устранения фиксированного размера p
массива. Это необязательно. Вместо того, чтобы хранить список людей для вывода позже, список, который будет переполняться, если в input.txt
будет более 50 человек, выведите их, как вы видите их в одном цикле.
PERSON p;
....
for (int i=0; i<n; ++i)
{
scan_check(fin,"%s%s", p.name, p.tel);
if (p.tel[1]=='3'){
fwrite(p.name, sizeof(char), strlen(p.name)+1, fout);
fwrite(p.tel, sizeof(char), strlen(p.tel)+1 , fout);
}
}
n
также не требуется. Нет необходимости в input.txt
, чтобы рассказать вам, сколько записей есть, вместо этого прочитайте файл до eof(fin)
. Это позволяет избежать ошибки n
.
вы * должны * проверить возвращаемые значения из всех системных вызовов. (например, fopen, fwrite, ...) – pm100
Но это txt-файл, в котором расположено каждое имя и номер телефона. Только эти имена и числа входят в двоичный файл, который является номерами RDS. – Zsombi
Используйте свой отладчик или поместите некоторые 'printf' в стратегических местах вашего кода, чтобы посмотреть, что происходит. –