Cのコンソールから行を読み取る方法は?


108

Cコンソールプログラムで行全体を読み取る最も簡単な方法は何ですか入力されたテキストは可変長である可能性があり、その内容については何も想定できません。


分かりますか?@ティムが下で言ったように、それはあなたが何を求めているかを混乱させます:)
ウォーレン

回答:


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を使用しないでください!境界チェックを行わず、バッファをオーバーフローさせる可能性があります


警告-reallocの結果をそこで確認する必要があります。しかし、それが失敗した場合は、より深刻な問題が発生する可能性が高くなります。
ティム・

4
おそらく、バッファでfgetsを実行し、最後に改行文字があるかどうかを確認することで、効率を少し向上させることができます。そうでない場合は、蓄積バッファを再割り当てし、そこにコピーして、再度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が標準から完全に削除された理由
Antti Haapala

6
モデレーターのメモ:上記のコメントは、回答の以前のリビジョンを
ロバートハーベイ

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を選択した場合よりもはるかに多くの間違いを犯すことになります;)


1
これは長い文字列を許可しません...-私は彼の質問の核心だと思います。
ティム・

2
-1、gets()は境界チェックを行わないため、使用しないでください。
アンワインド

7
一方、自分でプログラムを作成していて、入力を読み取るだけの場合は、これで十分です。プログラムに必要なセキュリティの量は仕様に匹敵します。毎回それを優先する必要はありません。
Martin Beckett、

4
@Tim-私はすべての履歴を保持したいと思います:)
Paul Kapustin

4
反対投票。getsこれはもう存在しないため、C11では機能しません。
Antti Haapala 16

11

バッファオーバーフローが発生しないようにし、入力を切り捨てないようにするには、1文字ずつ(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。これはgetline、任意のラインターミネータを含む単純なPOSIXスーパーセットです。

これは、増加が必要なときに割り当てられたメモリを2倍にし、スレッドセーフに見えます。

マクロを拡張する必要がありますが、それ以上のことはできません。


lenここでの目的は何ですか?読んでも長さが
Abdul

@Abdul参照してくださいman getlinelenは既存のバッファの長さで、0魔法であり、割り当てるように指示します。Readは、読み取った文字数です。バッファサイズはより大きい場合がありreadます。
Ciro Santilli郝海东冠状病六四事件法轮功

6

私と同じように、多くの人がこの投稿にアクセスしますが、タイトルは検索されるものと一致しますが、説明では可変長について述べています。ほとんどの場合、長さは事前にわかっています。

事前に長さがわかっている場合は、以下を試してください。

char str1[1001] = { 0 };
fgets(str1, 1001, stdin); // 1000 chars may be read

ソース:https : //www.tutorialspoint.com/c_standard_library/c_function_fgets.htm


5

提案されているように、行の終わりまたはEOFが返されるまでコンソールからgetchar()を使用して読み取り、独自のバッファーを構築できます。適切な最大行サイズを設定できない場合、動的にバッファが増大する可能性があります。

Cのnullで終了する文字列として行を取得する安全な方法として、fgetsを使用することもできます。

#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 []をオーバーフィルせず、正常に戻ったときに最後に受け入れられた文字の後にnullがあることを保証します。

行末に達した場合、終了 '\ 0'の前の文字は '\ n'になります。

終了 '\ 0'の前に終了 '\ n'がない場合は、さらにデータがあるか、次の要求がファイルの終わりを報告する可能性があります。どちらがどれかを判別するには、別のfgetsを実行する必要があります。(この点で、getchar()でループする方が簡単です。)

上記の(更新された)コード例では、fgetsが成功した後でline [sizeof(line)-1] == '\ 0'の場合、バッファーが完全に満たされていることがわかります。そのポジションが '\ n'によって進められている場合、あなたはラッキーだったことがわかります。それ以外の場合は、stdinの前にさらに多くのデータまたはファイルの終わりがあります。(バッファが完全に満たされていない場合でも、ファイルの終わりにいる可能性があり、現在の行の最後に「\ n」がない可能性があります。文字列をスキャンして検索および/またはまたは、文字列の終わりの前にある '\ n'(バッファ内の最初の '\ 0')を削除すると、最初にgetchar()を使用する傾向があります。)

最初のチャンクとして読み取った量よりも多くの行がある場合に対処するために必要なことを行います。バッファを動的に拡張する例は、getcharまたはfgetsのいずれかで機能するように作成できます。注意が必要なトリッキーなエッジケースがいくつかあります(次の入力が、バッファーが拡張される前に前の入力を終了した '\ 0'の位置から格納を開始することを覚えているなど)。


2

Cのコンソールから行を読み取る方法は?

  • 独自の関数を作成することは、コンソールから行を読み取るのに役立つ方法の1つです。

  • 動的メモリ割り当てを使用して、必要なメモリ量を割り当てています

  • 割り当てられたメモリを使い果たしそうになると、メモリのサイズを2倍にしようとします

  • そして、ここではループを使用してgetchar()、ユーザーが'\n'またはEOF文字を入力するまで、関数を使用して文字列の各文字を1つずつスキャンしています

  • 最後に、行を返す前に、追加で割り当てられたメモリを削除します

//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 2012

単一のグローバル変数has_errを使用してエラーを報告すると、この関数はスレッドに対して安全でなくなり、使いづらくなります。そのようにしないでください。NULLを返すことにより、すでにエラーを示しています。また、表示されたエラーメッセージは、汎用ライブラリ関数では良い考えではないと考える余地もあります。
ジョナサンレフラー

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);

linenullで終了していないし、含まれている\n(またはご使用のプラットフォームが使用しているものは何でも)が最後に。ストリームに対する次のI / O操作の後で無効になります。


はい、機能は存在します。nullで終了する文字列を提供しないという警告は十分に大きく、問題があるので、使用しない方がよいでしょう。これは危険です。
ジョナサンレフラー

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()関数を使用することです。これにより、一度に1文字を配列に格納します。

{
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)-1fgets終了ヌルのためのスペースを残します。
user102008

これwhile (!feof(file))は常に間違っていることに注意してください。これは誤った使用のもう1つの例にすぎません
ジョナサンレフラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.