Segfault во время sprintf()

Итак, в настоящее время я работаю над системным программированием для своего класса ОС Unix. Все, что должна сделать эта программа, это прочитать двоичный файл и вывести строки в CSV-файл. Я чувствую, что почти закончил, но по какой-то причине я продолжаю получать segfault.

Для уточнения: fd1 = входной файл, fd2 = выходной файл, numrecs = количество записей из входного файла. Где-то в main():

for(i=0;i<numrecs;i++){
    if((bin2csv(fd1, fd2)) == -1){
        printf("Error converting data.\n");
    }
}

int bin2csv(fd1, fd2){
    bin_record rec;
    char buffer[100];
    int buflen;
    strncpy(buffer,"\0", 100); /* fill buffer with NULL */
    recs = &rec;

    /* read in a record */
    if((buflen = read(fd1, &recs, sizeof(recs))) < 0){
        printf("Fatal Error: Data could not be read.\n");        
        return -1;
    }

   sprintf(buffer, "%d, %s, %s, %f, %d\n", recs->id, recs->lname, recs->fname, recs->gpa, recs->iq);
   printf("%s\n", buffer);
   write(fd2, buffer, sizeof(buffer));
   return 0;
}

Segfault возникает в строке «sprintf (буфер и т. д.);» однако я не могу понять, почему это происходит.

Это ошибка, которую выдает gdb:

Программа получила сигнал SIGSEGV, ошибка сегментации.
0x0000000100000c87 в bin2csv (fd1=3, fd2=4) в bin2csv.c:25
25 sprintf(buffer, "%d, %s, %s, %f, %d\n", записи->id, записи->lname,
записи->fname, записи->gpa, записи->iq);

Надеюсь, этой информации достаточно. Спасибо!


person Jake Parham    schedule 16.12.2015    source источник
comment
Ошибки сегментации вызваны плохим доступом к памяти, и, поскольку единственным доступом к памяти в этой строке является recs->..., вы можете предположить, что recs не инициализируется должным образом. Проверьте, было ли это NULL прямо перед этим, и если нет, продолжайте копать.   -  person Ricky Mutschlechner    schedule 16.12.2015
comment
Опубликовать определение bin_record.   -  person chux - Reinstate Monica    schedule 16.12.2015
comment
strncpy(buffer,"\0", 100); /* fill buffer with NULL */ Это не заполнит буфер символами NUL, поскольку strncpy() копирует максимальное количество байтов, но все равно останавливает копирование, когда в источнике встречается первый символ NUL.   -  person Andrew Henle    schedule 16.12.2015
comment
эта строка: int bin2csv(fd1, fd2){ не будет компилироваться. Возможно, вы имели в виду: (при использовании open()) int bin2csv( int fd1, int fd2 )   -  person user3629249    schedule 16.12.2015
comment
относительно этой строки: strncpy(buffer,"\0", 100); это скопирует до 0 байтов, а затем добавит «\ 0», вероятно, не то, что вы хотите. Предложить: memset( buffer, '\0', sizeof(buffer) );   -  person user3629249    schedule 16.12.2015
comment
относительно этой строки: recs = &rec;, rec определяется как struct , а не как указатель, и эта строка попросит указать на себя, возможно, не то, что вы хотите, предложите удалить эту строку.   -  person user3629249    schedule 16.12.2015
comment
после этой строки: if((buflen = read(fd1, &recs, sizeof(recs))) < 0){ и перед тем, как использовать содержимое recs для заполнения массива buffer[], необходимо проверить buflen==0 (то есть EOF) и buflen < sizeof(recs) (недостаточно символов в файле для чтения полного recs.   -  person user3629249    schedule 16.12.2015
comment
если только поля: recs->lname, recs->fname не имеют фиксированного размера, нельзя быть уверенным, что вся запись прочитана (а не часть следующей записи). Если нет фиксированной длины для этих двух полей, настоятельно рекомендуется читать по одному символу за раз и анализировать ввод (или используйте fscanf(), но это будет иметь свои проблемы)   -  person user3629249    schedule 16.12.2015
comment
относительно этой строки: write(fd2, buffer, sizeof(buffer)); нет гарантии, что массив buffer[] точно заполнен (он может быть меньше, чем полный, или даже переполнен (что приводит к неопределенному поведению). Что действительно необходимо, так это возвращаемое значение из sprintf(), которое код терпит неудачу сохранить, как длину (3-й) параметр.   -  person user3629249    schedule 16.12.2015
comment
относительно этих двух полей: recs->lname, recs->fname если эти поля не завершены NUL (и дополнены короткими именами) в исходном файле, sprintf() завершится сбоем.   -  person user3629249    schedule 16.12.2015
comment
@AndrewHenle Это тоже была моя внутренняя реакция, потому что это нестандартный способ обнуления буфера, но strncpy будет заполнять оставшуюся часть буфера нулями до тех пор, пока не будет записано n байтов.   -  person paddy    schedule 17.12.2015
comment
@user3629249 user3629249 Вы завалили комментарии точками, которые лучше подходят для ответа. Кроме того, только пара пунктов, которые вы сделали, действительно верны.   -  person paddy    schedule 17.12.2015
comment
@падди Да, действительно. strncpy() NUL дополняет целевую строку. Узнавайте что-то новое каждый день... :)   -  person Andrew Henle    schedule 17.12.2015
comment
@paddy, вопрос не содержит достаточно подробностей, чтобы гарантировать, что любой из моих комментариев является источником проблемы с ОП. (например, некоторые примеры входных записей, формат структуры recs и т. д.). Так что вполне возможно, что НИ ОДИН из моих комментариев не является ответом. В таких случаях я очень не решаюсь публиковать такие комментарии в качестве ответа.   -  person user3629249    schedule 17.12.2015
comment
при размещении вопроса о проблеме во время выполнения размещайте код, который компилируется чисто, образец ввода, желаемый вывод и т. д.   -  person user3629249    schedule 17.12.2015
comment
Это был мой первый пост, так что мне плохо, если что-то не очень хорошо объяснено. Я знаю, что трудно диагностировать проблемы, читая чужой код, особенно небольшую его часть. Тем не менее, спасибо за все отзывы!   -  person Jake Parham    schedule 17.12.2015


Ответы (4)


Похоже, что recs — это указатель. Вы читаете байты непосредственно в этот указатель, например, читаете необработанный адрес памяти из файла:

read(fd1, &recs, sizeof(recs))

И затем вы начинаете использовать его в вызове sprintf... БУМ!

На самом деле нет причин использовать его вообще (это глобальный?) ... Даже если вы инициализировали его с помощью recs = &rec и предполагая, что вы не выбрасываете его, он все равно не будет содержать действительный адрес вне этой функции. Это потому, что rec является локальной переменной.

Итак, просто прочитайте прямо в rec вот так:

read(fd1, &rec, sizeof(rec))

А затем в строке sprintf вы используете rec.id вместо recs->id (и т. д.).

person paddy    schedule 16.12.2015
comment
Это исправило ошибку сегментации! Спасибо за эту информацию. - person Jake Parham; 16.12.2015
comment
если recs является указателем, то эта строка: if((buflen = read(fd1, &recs, sizeof(recs))) < 0){ завершится ошибкой, потому что второй параметр read() должен быть указателем на входной буфер, а не указателем на указатель на входной буфер. в опубликованном коде ОП переменная recs не определена - person user3629249; 17.12.2015
comment
Спасибо за перефразирование моего ответа. - person paddy; 17.12.2015

Я вижу здесь несколько проблем:

  1. sprintf ничего не делает для предотвращения записи за конец строкового буфера. На самом деле он не знает длины этого буфера (в вашем случае 100 байт). Поскольку вы настроили буфер в стеке, если sprintf превысит ваш буфер (что может произойти с длинными именами или фамилиями или строками мусора в качестве входных данных), ваш стек будет поврежден, и, скорее всего, произойдет ошибка seg. Возможно, вы захотите включить логику, чтобы гарантировать, что sprintf не превысит объем имеющегося у вас буферного пространства. А еще лучше вообще избегать sprintf (подробнее об этом ниже)

  2. Вы не обрабатываете конец файла в предоставленном коде. Для конца файла чтение возвращает 0. Если вы передадите неверные указатели на sprintf, произойдет сбой.

  3. Используемые вами функции являются производными от UNIX (частью POSIX, но явно низкого уровня), которые используют небольшие целые числа в качестве файловых дескрипторов. Вместо этого я бы рекомендовал использовать основанные на FILE *. Интересующими функциями ввода-вывода будут fopen, fclose, fprintf, fwrite и т. д. Это избавит от необходимости использовать sprintf.

См. предыдущий вопрос для получения дополнительной информации.

person Ken Clement    schedule 16.12.2015
comment
Частью описания этого конкретного домашнего задания было то, что мы не могли использовать FILE * или какие-либо функции f*(). В противном случае я бы использовал их. - person Jake Parham; 16.12.2015
comment
@ Джейк Пархэм, достаточно честно. Я поддерживаю предложение Рахима ниже об использовании snprintf для защиты от переполнения буфера. Если вам не нужно создавать реентерабельный код в этом задании (вероятно), я также предлагаю вывести буфер из стека, пометив его как статический. Идентификатор по-прежнему будет локальным для функции. Удачи в вашем задании. - person Ken Clement; 17.12.2015

if((buflen = read(fd1, &recs, sizeof(recs))) < 0){

Используйте <= 0, а не < 0, иначе, когда возвращаемое значение равно 0, sprintf(buffer ... может зафиксировать ошибку при попытке разыменовать recs->id, значение которого не инициализировано.

person chux - Reinstate Monica    schedule 16.12.2015

У вас есть некоторые проблемы: 1) структура bin_record. Он имеет char[] и возможно переполнение. 2) в sprintf вы не можете установить максимальный размер буфера. лучше использовать snprintf следующим образом:

 sprintf(buffer, 100, "%d, %s, %s, %f, %d\n", recs->id, recs->lname, recs->fname, recs->gpa, recs->iq);

3) заполнить буфер нулем, используя это:

memset (buffer,'\0',100);
person Rahim Dastar    schedule 16.12.2015