콘솔에서 C로 줄을 읽는 방법은 무엇입니까?


108

C 콘솔 프로그램에서 전체 줄을 읽는 가장 간단한 방법은 무엇입니까 입력 된 텍스트는 가변 길이를 가질 수 있으며 내용에 대해 어떤 가정도 할 수 없습니다.


명확히 해주시 겠어요?
warren

답변:


81

동적 메모리 관리가 필요하고 fgets기능을 사용 하여 라인을 읽습니다. 그러나 얼마나 많은 문자를 읽었는지 확인할 방법이없는 것 같습니다. 따라서 fgetc를 사용합니다.

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

참고 : get을 사용하지 마십시오! 경계 검사를 수행하지 않으며 버퍼를 오버플로 할 수 있습니다.


주의 사항-거기에서 재 할당 결과를 확인해야합니다. 그러나 이것이 실패하면 더 나쁜 문제가있을 가능성이 높습니다.
Tim

4
버퍼로 fgets를 수행하고 끝에 개행 문자가 있는지 확인하여 효율성을 약간 향상시킬 수 있습니다. 그렇지 않은 경우 누적 버퍼를 재 할당하고 복사 한 다음 다시 가져옵니다.
Paul Tomblin

3
이 함수는 수정이 필요합니다. 라인 "len = lenmax;" realloc 이후는 realloc보다 선행하거나 "len = lenmax >> 1;"이어야합니다. -또는 길이의 절반이 이미 사용되었다는 사실을 설명하는 다른 등가물.
Matt Gallagher

1
@Johannes, 귀하의 질문에 대한 대답으로 @Paul의 접근 방식은 대부분의 libc 구현에서 더 빠를 수 있습니다. fgetc_unlocked스레드 안전성이 문제가 아니지만 성능이 중요하다면 덜 이식성이있는 것을 사용할 수 있습니다 .
vladr

3
이것은 getline()POSIX 표준 getline()기능 과 다릅니다 .
Jonathan Leffler 2017

28

GNU C 라이브러리 또는 다른 POSIX 호환 라이브러리를 사용 하는 경우 파일 스트림에 대해 사용 getline()하고 전달할 수 있습니다 stdin.


16

정적 할당을 위해 줄을 읽는 매우 간단하지만 안전하지 않은 구현 :

char line[1024];

scanf("%[^\n]", line);

버퍼 오버플로의 가능성은 없지만 전체 행을 읽지 않을 가능성이있는 더 안전한 구현은 다음과 같습니다.

char line[1024];

scanf("%1023[^\n]", line);

변수를 선언하는 지정된 길이와 형식 문자열에 지정된 길이 사이의 '1만큼 차이'가 아닙니다. 역사적인 유물입니다.


14
이것은 전혀 안전하지 않습니다. 그 이유는 정확히 같은 문제를 앓고 gets모두 표준에서 제거
안티 Haapala

6
중재자 메모 : 위의 의견은 답변
Robert Harvey

13

따라서 명령 인수를 찾고 있다면 Tim의 대답을 살펴보십시오. 콘솔에서 한 줄을 읽고 싶다면 :

#include <stdio.h>

int main()
{
  char string [256];
  printf ("Insert your full address: ");
  gets (string);
  printf ("Your address is: %s\n",string);
  return 0;
}

예, 안전하지 않습니다. 버퍼 오버런을 수행 할 수 있으며 파일 끝을 확인하지 않으며 인코딩 및 기타 많은 항목을 지원하지 않습니다. 사실 나는 그것이 이런 일을하는지 생각조차하지 않았다. 나는 내가 약간 망친 것에 동의한다 :)하지만 ... "어떻게 C로 콘솔에서 한 줄을 읽는가?"와 같은 질문을 볼 때, 나는 사람이 100 줄의 코드가 아닌 gets ()와 같은 간단한 것을 필요로한다고 가정한다. 위와 같이. 실제로 100 줄의 코드를 실제로 작성하려고하면 실수를 더 많이하게 될 것이라고 생각합니다.


1
이것은 긴 문자열을 허용하지 않습니다 ...-나는 그의 질문의 핵심이라고 생각합니다.
Tim

2
-1, gets ()는 경계 검사를하지 않기 때문에 사용해서는 안됩니다.
언 와인드

7
반면에 프로그램을 직접 작성하고 입력을 읽어야한다면 완벽하게 괜찮습니다. 프로그램에 필요한 보안 수준은 사양과 비슷합니다. 매번이를 우선 순위로 두지 않아도됩니다.
Martin Beckett

4
@ 팀-나는 모든 역사를 유지하고 싶다 :)
Paul Kapustin

4
비추천. gets더 이상 존재하지 않으므로 C11에서는 작동하지 않습니다.
Antti Haapala

11

버퍼 오버플로가없고 입력을 자르지 않도록 문자 별 (getc ()) 루프를 사용해야 할 수도 있습니다.


9

getline 실행 가능한 예

이 답변에 대해 언급 했지만 여기에 예가 있습니다.

그것은 POSIX 7 이고, 우리를 위해 메모리를 할당하고, 할당 된 버퍼를 루프에서 멋지게 재사용합니다.

포인터 newbs, 이것을 읽으십시오 : 왜 getline의 첫 번째 인수가 "char *"대신 "char **"포인터에 대한 포인터입니까?

#define _XOPEN_SOURCE 700

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

int main(void) {
    char *line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    while (read != -1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

glibc 구현

POSIX가 없습니까? 아마도 glibc 2.23 구현 을보고 싶을 입니다.

이는 임의의 줄 종결자가 getdelim있는 단순 POSIX 상위 집합 인 getline으로 확인됩니다.

증가가 필요할 때마다 할당 된 메모리를 두 배로 늘리고 스레드로부터 안전 해 보입니다.

매크로 확장이 필요하지만 훨씬 더 잘할 수는 없습니다.


len여기에 있는 목적은 무엇입니까 , 읽을 때 길이도 제공됩니다
Abdul

@ Abdul 참조하십시오 man getline. len는 기존 버퍼의 길이이며, 0마법이며 할당하도록 지시합니다. 읽기는 읽은 문자 수입니다. 버퍼 크기는보다 클 수 있습니다 read.
치로 틸리는郝海东冠状病六四事件法轮功


5

제안 된대로 getchar ()를 사용하여 줄 끝이나 EOF가 반환 될 때까지 콘솔에서 읽을 수 있으며 자체 버퍼를 구축 할 수 있습니다. 적절한 최대 라인 크기를 설정할 수없는 경우 버퍼가 동적으로 증가 할 수 있습니다.

또한 fgets를 사용하여 C null로 끝나는 문자열로 라인을 얻는 안전한 방법을 사용할 수 있습니다.

#include <stdio.h>

char line[1024];  /* Generously large value for most situations */

char *eof;

line[0] = '\0'; /* Ensure empty line if no input delivered */
line[sizeof(line)-1] = ~'\0';  /* Ensure no false-null at end of buffer */

eof = fgets(line, sizeof(line), stdin);

콘솔 입력을 모두 사용했거나 어떤 이유로 작업이 실패한 경우 eof == NULL이 반환되고 라인 버퍼가 변경되지 않을 수 있습니다 (이것이 첫 번째 문자를 '\ 0'으로 설정하는 것이 편리한 이유입니다).

fgets는 line []을 넘치지 않으며 성공적인 리턴시 마지막으로 승인 된 문자 뒤에 널이 있는지 확인합니다.

줄 끝에 도달하면 '\ 0'으로 끝나는 앞의 문자는 '\ n'이됩니다.

종료 '\ 0'전에 종료 '\ n'이 없으면 더 많은 데이터가 있거나 다음 요청이 파일 끝을보고 할 수 있습니다. 어느 것이 무엇인지 결정하기 위해 다른 fgets를 수행해야합니다. (이 점에서 getchar ()로 반복하는 것이 더 쉽습니다.)

위의 (업데이트 된) 예제 코드에서, 성공적인 fgets 후 line [sizeof (line) -1] == '\ 0'이면 버퍼가 완전히 채워진 것을 알 수 있습니다. 그 위치가 '\ n'에 의해 진행된다면 당신은 운이 좋았다는 것을 알고 있습니다. 그렇지 않으면 stdin에 더 많은 데이터 또는 파일 끝이 있습니다. (버퍼가 완전히 채워지지 않은 경우에도 여전히 파일의 끝에있을 수 있으며 현재 줄 끝에 '\ n'이 없을 수도 있습니다. 문자열을 검색하여 찾아야하기 때문에 / 또는 문자열 끝 (버퍼의 첫 번째 '\ 0') 앞의 '\ n'을 제거하면 처음부터 getchar ()를 사용하는 것을 선호합니다.)

첫 번째 청크로 읽은 양보다 더 많은 줄이있는 것을 처리하기 위해해야 ​​할 일을하십시오. 동적으로 늘어나는 버퍼의 예는 getchar 또는 fget과 함께 작동하도록 만들 수 있습니다. 주의해야 할 몇 가지 까다로운 경우가 있습니다 (예 : 버퍼가 확장되기 전에 이전 입력을 종료 한 '\ 0'위치에 다음 입력이 저장되기 시작하는 것을 기억하는 것과 같은).


2

콘솔에서 C로 줄을 읽는 방법은 무엇입니까?

  • 자신의 기능을 구축하는 것은 콘솔에서 한 줄을 읽는 데 도움이되는 방법 중 하나입니다.

  • 필요한 메모리 양을 할당 하기 위해 동적 메모리 할당 을 사용 하고 있습니다.

  • 할당 된 메모리를 모두 사용하려고 할 때 메모리 크기를 두 배로 늘리려 고합니다.

  • 그리고 여기 getchar()에서는 사용자가 입력 '\n'하거나 EOF문자를 입력 할 때까지 함수를 사용하여 문자열의 각 문자를 하나씩 스캔하는 루프를 사용하고 있습니다.

  • 마지막으로 라인을 반환하기 전에 추가로 할당 된 메모리를 제거합니다.

//the function to read lines of variable length

char* scan_line(char *line)
{
    int ch;             // as getchar() returns `int`
    long capacity = 0;  // capacity of the buffer
    long length = 0;    // maintains the length of the string
    char *temp = NULL;  // use additional pointer to perform allocations in order to avoid memory leaks

    while ( ((ch = getchar()) != '\n') && (ch != EOF) )
    {
        if((length + 1) >= capacity)
        {
            // resetting capacity
            if (capacity == 0)
                capacity = 2; // some initial fixed length 
            else
                capacity *= 2; // double the size

            // try reallocating the memory
            if( (temp = realloc(line, capacity * sizeof(char))) == NULL ) //allocating memory
            {
                printf("ERROR: unsuccessful allocation");
                // return line; or you can exit
                exit(1);
            }

            line = temp;
        }

        line[length] = (char) ch; //type casting `int` to `char`
    }
    line[length + 1] = '\0'; //inserting null character at the end

    // remove additionally allocated memory
    if( (temp = realloc(line, (length + 1) * sizeof(char))) == NULL )
    {
        printf("ERROR: unsuccessful allocation");
        // return line; or you can exit
        exit(1);
    }

    line = temp;
    return line;
}
  • 이제 다음과 같이 전체 줄을 읽을 수 있습니다.

    char *line = NULL;
    line = scan_line(line);

다음 은 함수를 사용 하는 예제 프로그램입니다scan_line() .

#include <stdio.h>
#include <stdlib.h> //for dynamic allocation functions

char* scan_line(char *line)
{
    ..........
}

int main(void)
{
    char *a = NULL;

    a = scan_line(a); //function call to scan the line

    printf("%s\n",a); //printing the scanned line

    free(a); //don't forget to free the malloc'd pointer
}

샘플 입력 :

Twinkle Twinkle little star.... in the sky!

샘플 출력 :

Twinkle Twinkle little star.... in the sky!

0

나는 얼마 전에 같은 문제를 만났고 이것이 나의 해결책이었습니다.

/*
 * Initial size of the read buffer
 */
#define DEFAULT_BUFFER 1024

/*
 * Standard boolean type definition
 */
typedef enum{ false = 0, true = 1 }bool;

/*
 * Flags errors in pointer returning functions
 */
bool has_err = false;

/*
 * Reads the next line of text from file and returns it.
 * The line must be free()d afterwards.
 *
 * This function will segfault on binary data.
 */
char *readLine(FILE *file){
    char *buffer   = NULL;
    char *tmp_buf  = NULL;
    bool line_read = false;
    int  iteration = 0;
    int  offset    = 0;

    if(file == NULL){
        fprintf(stderr, "readLine: NULL file pointer passed!\n");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!\n");
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        if(fgets(tmp_buf, DEFAULT_BUFFER, file) == NULL){
            free(tmp_buf);

            break;
        }

        if(tmp_buf[strlen(tmp_buf) - 1] == '\n') /* we have an end of line */
            line_read = true;

        offset = DEFAULT_BUFFER * (iteration + 1);

        if((buffer = realloc(buffer, offset)) == NULL){
            fprintf(stderr, "readLine: Unable to reallocate buffer!\n");
            free(tmp_buf);
            has_err = true;

            return NULL;
        }

        offset = DEFAULT_BUFFER * iteration - iteration;

        if(memcpy(buffer + offset, tmp_buf, DEFAULT_BUFFER) == NULL){
            fprintf(stderr, "readLine: Cannot copy to buffer\n");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}

1
goto오류 사례를 처리하는 데 사용하면 코드가 훨씬 간단 해집니다 . 그럼에도 불구하고 루프에서 동일한 크기로 반복하는 tmp_buf대신 재사용 할 수 있다고 생각하지 malloc않습니까?
Shahbaz

단일 전역 변수 has_err를 사용하여 오류를보고하면이 함수가 스레드에 안전하지 않고 사용하기가 불편 해집니다. 그렇게하지 마십시오. NULL을 반환하여 이미 오류를 나타냅니다. 또한 인쇄 된 오류 메시지가 범용 라이브러리 함수에서 좋은 생각이 아니라고 생각할 여지가 있습니다.
Jonathan Leffler

0

BSD 시스템 및 Android에서는 다음을 사용할 수도 있습니다 fgetln.

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

이렇게 :

size_t line_len;
const char *line = fgetln(stdin, &line_len);

line널 (null) 종료하지 않고 포함 \n(또는 플랫폼을 사용하고 무엇이든)은 결국. 스트림에서 다음 I / O 작업 후에는 무효화됩니다.


예, 기능이 있습니다. null로 끝나는 문자열을 제공하지 않는다는 경고는 충분히 크고 문제가 있으므로 사용하지 않는 것이 좋습니다. 위험합니다.
Jonathan Leffler

0

이 같은:

unsigned int getConsoleInput(char **pStrBfr) //pass in pointer to char pointer, returns size of buffer
{
    char * strbfr;
    int c;
    unsigned int i;
    i = 0;
    strbfr = (char*)malloc(sizeof(char));
    if(strbfr==NULL) goto error;
    while( (c = getchar()) != '\n' && c != EOF )
    {
        strbfr[i] = (char)c;
        i++;
        strbfr = (void*)realloc((void*)strbfr,sizeof(char)*(i+1));
        //on realloc error, NULL is returned but original buffer is unchanged
        //NOTE: the buffer WILL NOT be NULL terminated since last
        //chracter came from console
        if(strbfr==NULL) goto error;
    }
    strbfr[i] = '\0';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

0

콘솔에서 한 줄을 읽는 가장 쉽고 간단한 방법은 getchar () 함수를 사용하여 한 번에 한 문자를 배열에 저장하는 것입니다.

{
char message[N];        /* character array for the message, you can always change the character length */
int i = 0;          /* loop counter */

printf( "Enter a message: " );
message[i] = getchar();    /* get the first character */
while( message[i] != '\n' ){
    message[++i] = getchar(); /* gets the next character */
}

printf( "Entered message is:" );
for( i = 0; i < N; i++ )
    printf( "%c", message[i] );

return ( 0 );

}


-3

이 함수는 원하는 작업을 수행해야합니다.

char* readLine( FILE* file )
 {
 char buffer[1024];
 char* result = 0;
 int length = 0;

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer), file );
  int len = strlen(buffer);
  buffer[len] = 0;

  length += len;
  char* tmp = (char*)malloc(length+1);
  tmp[0] = 0;

  if( result )
   {
   strcpy( tmp, result );
   free( result );
   result = tmp;
   }

  strcat( result, buffer );

  if( strstr( buffer, "\n" ) break;
  }

 return result;
 }

char* line = readLine( stdin );
/* Use it */
free( line );

이게 도움이 되길 바란다.


1
하지 fgets( buffer, sizeof(buffer), file );말아야 sizeof(buffer)-1합니다. fgets종료 널을위한 공간을 남깁니다.
user102008 2010

참고 while (!feof(file))항상 잘못 이 잘못된 사용의 또 하나의 예입니다.
Jonathan Leffler 2017
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.