Можно ли правильно читать нулевые символы, используя fgets или gets_s? - PullRequest
0 голосов
/ 03 мая 2018

Предположим, я хочу прочитать из stdin и разрешить пользователю вводить строки, содержащие нулевые символы. Возможно ли это с функциями строкового ввода, такими как fgets или gets_s? Или я должен использовать, например, fgetc или fread?

Кто-то здесь хотел это сделать.

Ответы [ 4 ]

0 голосов
/ 06 мая 2018

Можно ли правильно читать нулевые символы, используя fgets или gets_s?

Как показывают некоторые другие ответы, ответ, по-видимому, «Да, только едва». Точно так же можно забивать гвозди с помощью отвертки. Точно так же можно написать (что составляет) код BASIC или FORTRAN на C.

Но ни одна из этих вещей не является хорошей идеей. Используйте правильный инструмент для работы. Если вы хотите забить гвозди, используйте молоток. Если вы хотите написать BASIC или FORTRAN, используйте интерпретатор BASIC или компилятор FORTRAN. И если вы хотите прочитать двоичные данные, которые могут содержать нулевые символы, используйте fread (или, возможно, getc). Не используйте fgets, потому что его интерфейс никогда не был предназначен для этой задачи.

0 голосов
/ 03 мая 2018

Можно ли правильно прочитать нулевые символы, используя fgets или gets_s?

Не совсем.

fgets() не указано, чтобы оставить оставшуюся часть буфера в одиночку (после добавления '\0'), поэтому предварительная загрузка буфера для пост-анализа не указана для работы.

В случае ошибка чтения буфер указывается как "содержимое массива неопределенный ", но этот случай может быть исключен из дальнейшего рассмотрения путем проверки возвращаемого значения.

Если бы не это, то сработал бы различный тест, подобный предложенному @ R .. .

  char buf[80];
  int length = 0;
  memset(buf, sizeof buf, '\n');
  // Check return value before reading `buf`.
  if (fgets(buf, sizeof buf, stdin)) {
    // The buffer should end with a \0 and 0 to 78 \n
    // Starting at the end, look for the first non-\n
    int i = sizeof buf - 1;
    while (i > 0) {
      if (buf[i] != '\n') {
        if (buf[i] == '\0') {
          // found appended null
          length = i;
        } else {
          length = -1;  // indeterminent length
        }
        break;
      }
      i--;
    }
    if (i == 0) {
      // entire buffer was \n
      length = -1;  // indeterminent length
    }
  }

fgets() просто не в состоянии читать пользовательский ввод , который может содержать нулевых символов . Это остается дырой в C.

Я пытался закодировать эту fgets () альтернативу , хотя я не полностью удовлетворен этим.

0 голосов
/ 05 мая 2018

Есть способ надежно обнаружить присутствие \0 символов, прочитанных fgets(3), но это очень неэффективно. Чтобы надежно определить, что из входного потока прочитан нулевой символ, вы должны сначала заполнить буфер ненулевыми символами. Причина этого в том, что fgets() разграничивает его ввод, помещая \0 в конец ввода и (как предполагается) ничего не записывает после этого символа.

Хорошо, после заполнения входного буфера, скажем, \001 символов, вызовите fgets() в вашем буфере и начните поиск с конца буфера в обратном направлении для символа \0 Это конец входного буфера. Нет необходимости проверять символ раньше (единственный случай, когда он не будет \n, это если последний символ равен \0, а строка ввода была длиннее, чем пробел в буфере для полной строки с нулевым символом в конце, или фиктивная реализация fgets(3) (есть некоторые). С самого начала вы можете иметь как можно больше \0 s, но не волнуйтесь, они из входного потока.

Как видите, это довольно неэффективно.

#define NON_ZERO         1
#define BOGUS_FGETS      -2 /* -1 is used by EOF */

/**
 * variant of fgets that returns the number of characters actually read */
ssize_t variant_of_fgets(const char *buffer, const size_t sz, FILE *in)
{
    /* set buffer to non zero value */
    memset(buffer, NON_ZERO, sz);

    /* do actual fgets */
    if (!fgets(buffer, sizeof buffer, stdin)) {
        /* EOF */
        return EOF;
    }
    char *p = buffer + sizeof buffer; 
    while (--p >= buffer)
        if (!*p) 
            break; /* if char is a \0 we're out */
    /* ASSERT: (p < buffer)[not-found] || (p >= buffer)[found] */
    if (p <= buffer) { 
        /* Why do we check for p <= buffer ?
         * p must be > buffer, as if p == buffer
         * the implementation must be also bogus, because
         * the returned string should be an empty string "".
         * this can happen only with a bogus implementation
         * or an absurd buffer of length one (with only place for
         * the \0 char).  Else, it must be a read character
         * (it can be a \0, but then it must have another \0 
         * behind, and p must be greater than this) */
        return BOGUS_FGETS;
    }
    /* ASSERT: p > buffer && p < buffer + sz  [found a \0] 
     * p points to the position of the last \0 in the buffer */ 

    return p - buffer;  /* this is the string length */
} /* variant_of_fgets */ 
* * Пример 1 022

Следующий пример кода проиллюстрирует эту вещь, сначала пример выполнения:

$ pru
===============================================
<OFFSET> : pru.c:24:main: buffer initial contents
00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : ................
00000010 : e0 dd cf eb 02 56 00 00 e0 d7 cf eb 02 56 00 00 : .....V.......V..
00000020
<OFFSET> : pru.c:30:main: buffer after memset
00000000 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000010 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000020
^@^@^@^@^D^D
<OFFSET> : pru.c:41:main: buffer after fgets(returned size should be 4)
00000000 : 00 00 00 00 00 fa fa fa fa fa fa fa fa fa fa fa : ................
00000010 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000020
===============================================
<OFFSET> : pru.c:24:main: buffer initial contents
00000000 : 00 00 00 00 00 fa fa fa fa fa fa fa fa fa fa fa : ................
00000010 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000020
<OFFSET> : pru.c:30:main: buffer after memset
00000000 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000010 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000020
^D
<OFFSET> : pru.c:41:main: buffer after fgets(returned size should be 0)
00000000 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000010 : fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa : ................
00000020
===============================================
pru.c:45:main: END OF PROGRAM
$ _

Makefile

RM ?= rm -f

targets = pru
toclean += $(targets)

all: $(targets)
clean:
    $(RM) $(toclean)

pru_objs = pru.o fprintbuf.o
toclean += $(pru_objs)

pru: $(pru_objs)
    $(CC) -o $@ $($@_objs)

pru.c

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

#include "fprintbuf.h"

#define F(fmt) __FILE__":%d:%s: " fmt, __LINE__, __func__

void line()
{
    puts("===============================================");
}
int main()
{
    uint8_t buffer[32];
    int eof;

    line();
    do {
        fprintbuf(stdout,
                buffer, sizeof buffer, 
                F("buffer initial contents"));

        memset(buffer, 0xfa, sizeof buffer);

        fprintbuf(stdout,
                buffer, sizeof buffer, 
                F("buffer after memset"));

        eof = !fgets(buffer, sizeof buffer, stdin);

        /* search for the last \0 */
        uint8_t *p = buffer + sizeof buffer;
        while (*--p && (p > buffer))
            continue;

        if (p <= buffer)
            printf(F("BOGUS implementation"));

        fprintbuf(stdout,
                buffer, sizeof buffer,
                F("buffer after fgets(size should be %u)"),
                p - buffer);
        line();
    } while(!eof);
}

со вспомогательной функцией для печати содержимого буфера:

fprintbuf.h

/* $Id: fprintbuf.h,v 2.0 2005-10-04 14:54:49 luis Exp $
 * Author: Luis Colorado <Luis.Colorado@HispaLinux.ES>
 * Date: Thu Aug 18 15:47:09 CEST 2005
 *
 * Disclaimer:
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#ifndef FPRINTBUF_H
#define FPRINTBUF_H

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#include <stdio.h>
#include <stdint.h>

size_t fprintbuf (
    FILE               *f,      /* fichero de salida */
    const uint8_t      *b,      /* puntero al buffer */
    size_t              t,      /* tamano del buffer */
    const char         *fmt,    /* rotulo de cabecera */
                        ...);

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */

#endif /* FPRINTBUF_H */

fprintbuf.c

/* $Id: fprintbuf.c,v 2.0 2005-10-04 14:54:49 luis Exp $
 * AUTHOR: Luis Colorado <licolorado@indra.es>
 * DATE: 7.10.92.
 * DESC: muestra un buffer de datos en hexadecimal y ASCII.
 */

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include "fprintbuf.h"

#define     TAM_REG         16

size_t
fprintbuf(
        FILE           *f,      /* fichero de salida */
        const uint8_t  *b,      /* puntero al buffer */
        size_t          t,      /* tamano del buffer */
        const char     *fmt,    /* rotulo de cabecera */
                        ...)
{
    size_t off, i;
    uint8_t c;
    va_list lista;
    size_t escritos = 0;

    if (fmt)
            escritos += fprintf (f, "<OFFSET> : ");
    va_start (lista, fmt);
    escritos += vfprintf (f, fmt, lista);
    va_end (lista);
    escritos += fprintf (f, "\n");
    off = 0;
    while (t > 0) {
            escritos += fprintf (f, "%08lx : ", off);
            for (i = 0; i < TAM_REG; i++) {
                    if (t > 0)
                            escritos += fprintf (f, "%02x ", *b);
                    else escritos += fprintf (f, "   ");
                    off++;
                    t--;
                    b++;
            }
            escritos += fprintf (f, ": ");
            t += TAM_REG;
            b -= TAM_REG;
            off -= TAM_REG;
            for (i = 0; i < TAM_REG; i++) {
                    c = *b++;
                    if (t > 0)
                            if (isprint (c))
                                    escritos += fprintf (f, "%c", c);
                            else    escritos += fprintf (f, ".");
                    else break;
                    off++;
                    t--;
            }
            escritos += fprintf (f, "\n");
    }
    escritos += fprintf (f, "%08lx\n", off);

    return escritos;
} /* fprintbuf */
0 голосов
/ 03 мая 2018

Для fgets, да. fgets задается так, как будто он повторяется fgetc и хранит результирующие символы в массиве. Никаких специальных положений для нулевого символа не предусмотрено, за исключением того, что в дополнение к прочитанным символам в конце хранится нулевой символ (после последнего символа).

Чтобы успешно отличить встроенные нулевые символы от окончания, требуется определенная работа.

Во-первых, предварительно заполните буфер '\n' (например, используя memset). Теперь, когда возвращается fgets, ищите первый '\n' в буфере (например, используя memchr).

  • Если '\n', fgets не остановлено из-за заполнения выходного буфера, и все, кроме последнего байта (нулевого терминатора), являются данными, которые были прочитаны из файла.

  • Если за первым '\n' сразу же следует '\0' (нулевое завершение), fgets останавливается из-за достижения новой строки, и все данные до этой новой строки читаются из файла.

  • Если за первым '\n' не следует '\0' (либо в конце буфера, либо после другого '\n'), то fgets останавливается из-за EOF или ошибки, и все до байта непосредственно перед тем, как '\n' (что обязательно является '\0'), но не включая его, было прочитано из файла.

Для gets_s я понятия не имею, и я настоятельно рекомендую не использовать его. Единственная широко реализованная версия функций "* _s" Приложения K, Microsoft, даже не соответствует спецификациям, которые они выдвинули в приложение стандарта C, и, как сообщается, имеет проблемы, которые могут сделать этот подход неработоспособным.

...