c fput c не возвращает ошибку, когда я думаю, что это должно быть на linux, работает должным образом на windows - PullRequest
2 голосов
/ 13 июля 2020

Я пытаюсь использовать c библиотечную функцию fput c из stdio.h

Я предполагаю, что она должна работать согласно spe c at https://linux.die.net/man/3/fputc

В частности, интересующие части:

  • fput c () записывает символ c, приведенный к беззнаковому char, в поток.
  • fput c (), put c () и putchar () возвращают символ, записанный как беззнаковый char, преобразованный в int или EOF в случае ошибки.

На основе по этой информации я предполагаю, что если fput c успешно записывает символ в предоставленный поток, я должен получить возвращаемое значение, равное записанному символу, а если он не может записать в поток, я должен получить значение EOF .

Вот мой код:

// COMPILE
//    gcc -Wall -Wextra -Werror -O3 -s ./fputc.c -o ./fputc-test
// RUN
//    ./fputc-test

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void print_error_exit();

int main() {
    FILE * fp;
    
    // ensure file exists
    if((fp = fopen("file", "w")) == NULL) print_error_exit();
    
    // close stream
    if(fclose(fp) == EOF) print_error_exit();
    
    // open file in read-only mode
    if((fp = fopen("file", "r")) == NULL) print_error_exit();
    
    // close stream
    if(fclose(fp) == EOF) print_error_exit();
    
    printf("EOF is: %d\n", EOF);
    // use fputc on a read-only closed stream (should return error, right?)
    printf("fputc returned: %d\n", fputc(97, fp)); // 97 is ascii for 'a'
    // expected:
    //    prints EOF (probably -1)
    // actual:
    //    prints 97 on linux with both gcc and clang (multiple versions tested)
    //    prints -1 on windows with mingw-gcc
    
    return EXIT_SUCCESS;
}

void print_error_exit() {
    fprintf(stderr, "%s\n", strerror(errno));
    exit(EXIT_FAILURE);
}

Я тестировал код на Ubuntu 20, Debian 9, Windows 10, используя g cc 8.1.0, g cc 8.3.0, g cc 9.3.0 и clang 7.0.1. На windows я использовал mingw.

Единственная тенденция, которую я обнаружил, заключается в том, что fput c возвращает то, что я ожидал от него на windows, и не возвращает то, что я ожидал от него. linux.

Что из следующего является правильным?

  • В моем коде есть ошибка (если есть, объясните, почему, и опубликуйте исправленный код, пожалуйста)
  • Я неправильно понял SPE c (если так, объясните лучше)
  • Есть ошибка как в g cc, так и в clang при компиляции для linux (где сообщить это?)
  • Есть ошибка linux (некоторые дистрибутивы или все) (где сообщить об этом?)

Пожалуйста, помогите мне разобраться в этом. Почему fput c не возвращает код ошибки (EOF), когда я пытаюсь использовать его в закрытом потоке, не говоря уже о потоке, который был открыт только для чтения?

Ответы [ 2 ]

7 голосов
/ 13 июля 2020

Ваш код демонстрирует неопределенное поведение.

Из J.2 Неопределенное поведение :

Поведение не определено в следующих случаях:

Значение указателя на объект FILE используется после закрытия связанного файла (7.21.3).

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

5 голосов
/ 13 июля 2020

C17 7.21.3 (4):

Значение указателя на объект FILE не определено после закрытия связанного файла.

Так в вашем fputc вызов, fp имеет неопределенное значение. Стандарт определяет поведение для fputc только тогда, когда fp указывает на выходной поток, и мы не можем сказать, что это так, поэтому поведение не определено.

Текст немного вводит в заблуждение, потому что на типичная система, значение указателя не меняется при передаче в fclose; в конце концов, C передает аргументы по значению, поэтому FILE *fp; ...; fclose(fp); не может изменить значение вашей fp переменной, даже если бы захотел. Он по-прежнему указывает на тот же адрес, что и всегда. Но данные, расположенные по этому адресу, определенно могут стать неопределенными, и для системы больше не должно иметь смысла интерпретировать их как поток.

Вот что происходит под капотом Linux. Он должен go, не говоря, что это все детали реализации, и вы не должны полагаться на них в какой-либо программе.

Вы можете увидеть здесь что Linux на самом деле fputc делает. Параметр fp указывает на объект FILE, который содержит указатель на буфер и числа, указывающие, сколько места осталось. Если в буфере есть место, туда записывается символ; это не может потерпеть неудачу. Только если буфер заполняется и данные должны быть записаны через ОС, есть возможность для fputc вернуть ошибку.

Теперь, когда вы fclose файл, буфер и FILE объект просто освобождается с помощью free() или аналогичного механизма. Если за это время никто другой не выделил память, содержимое этих объектов может все еще находиться в памяти и не измениться к моменту вызова fputc. Эти объекты никоим образом не помечаются как недопустимые до этого, потому что никто никогда не увидит флаг, если они не будут обращаться к освобожденной памяти, и никакая правильная программа никогда не должна этого делать. Поэтому, когда вы вызываете fputc, содержимое памяти, на которое указывает fp, по-прежнему выглядит точно так же, как действительный объект FILE, с не заполненным буфером, поэтому fp записывает туда символ и возвращает успех - потому что, в конце концов, запись символа в буфер невозможна. Но на самом деле теперь вы записали в освобожденную память, и это потенциально может привести к всевозможным проблемам.

Это похоже на ошибки «использования после освобождения» с mallo c: система не доверяет вам использовать ресурс после того, как вы его выпустили, но не обещает поймать вас, если вы это сделаете.

Другие системы, которые вы тестировали, скорее всего, имеют какой-то «недействительный» флаг в объекте FILE, который они устанавливают перед его освобождением, и их fputc, вероятно, проверяет этот флаг. Таким образом, если объект FILE не был перезаписан другим материалом, значение флага все еще может быть в памяти, и поэтому fputc не выполняется. Но это все равно неправильно, потому что ему нужно было прочитать освобожденную память, чтобы даже увидеть флаг. Если вы проделаете кучу дополнительных работ между ними, включая выделение большего объема памяти и запись в нее, вы также можете увидеть более непредсказуемое неправильное поведение в этих системах, возможно, включая ложные результаты успеха.

...