C コンソール プログラムで行全体を読み取る最も簡単な方法は何ですか? 入力されたテキストは可変長である可能性があり、その内容について推測することはできません。
13 に答える
動的メモリ管理が必要で、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;
}
注: gets は絶対に使用しないでください。境界チェックを行わず、バッファをオーバーフローさせる可能性があります
GNU C ライブラリまたは別の POSIX 準拠のライブラリを使用している場合は、ファイル ストリームに使用getline()
して渡すことができます。stdin
静的割り当ての行を読み取るための非常に単純ですが安全ではない実装:
char line[1024];
scanf("%[^\n]", line);
バッファオーバーフローの可能性はないが、行全体を読み取らない可能性がある、より安全な実装は次のとおりです。
char line[1024];
scanf("%1023[^\n]", line);
変数を宣言するために指定された長さとフォーマット文字列で指定された長さの「1による差」ではありません。それは歴史的な遺物です。
したがって、コマンド引数を探していた場合は、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 行のコードを書こうとすると、gets を選択した場合よりも多くの間違いを犯すことになると思います ;)
getline
実行可能な例
getline
この回答で言及されましたが、ここに例があります。
これはPOSIX 7であり、メモリを割り当て、割り当てられたバッファをループで適切に再利用します。
ポインター newbs、これを読んでください: getline の最初の引数は、"char*" ではなくポインター "char**" へのポインターであるのはなぜですか?
main.c
#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 (1) {
puts("enter a line");
read = getline(&line, &len, stdin);
if (read == -1)
break;
printf("line = %s", line);
printf("line length = %zu\n", read);
puts("");
}
free(line);
return 0;
}
コンパイルして実行します。
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
結果:これはターミナルに表示されます:
enter a line
次に、次のように入力します。
asdf
Enter キーを押すと、次のように表示されます。
line = asdf
line length = 5
続いて別のもの:
enter a line
または、パイプから標準入力へ:
printf 'asdf\nqwer\n' | ./main.out
与えます:
enter a line
line = asdf
line length = 5
enter a line
line = qwer
line length = 5
enter a line
Ubuntu 20.04 でテスト済み。
glibc の実装
いいえPOSIX?glibc 2.23の実装を見たいと思うかもしれません。
これは、任意の行終端記号を持つgetdelim
の単純な POSIX スーパーセットです。getline
増加が必要なときはいつでも割り当てられたメモリを 2 倍にし、スレッドセーフに見えます。
いくつかのマクロ展開が必要ですが、それ以上のことはまずありません。
バッファ オーバーフローが発生せず、入力が切り捨てられないようにするために、1 文字ずつ (getc()) ループを使用する必要がある場合があります。
示唆されているように、 getchar() を使用して、行末または EOF が返されるまでコンソールから読み取り、独自のバッファーを構築できます。妥当な最大行サイズを設定できない場合、バッファが動的に拡大する可能性があります。
行を 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」がない可能性もあります。文字列をスキャンして検索する必要があるため、および/または、文字列の末尾 (バッファ内の最初の '\0') の前にある '\n' を削除する場合、最初に getchar() を使用することを好む傾向があります。)
最初のチャンクとして読み取った量よりも多くの行がまだあることに対処するために必要なことを行います。バッファーを動的に拡張する例は、getchar または fgets で動作するように作成できます。注意すべきトリッキーなエッジケースがいくつかあります (バッファが拡張される前に、前の入力を終了した '\0' の位置に次の入力を格納し始めることを忘れないようにするなど)。
Cでコンソールから行を読み取る方法は?
独自の関数を構築することは、コンソールから行を読み取るのに役立つ方法の 1 つです。
動的メモリ割り当てを使用して、必要な量のメモリを割り当てています
割り当てられたメモリを使い果たしそうになると、メモリのサイズを 2 倍にしようとします。
そしてここでは、ループを使用して、ユーザーが入力または文字
getchar()
を入力するまで、関数を使用して文字列の各文字を 1 つずつスキャンしています。'\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`
length++;
}
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!
私はしばらく前に同じ問題に遭遇しました、これは私の解決策でした、それが役立つことを願っています。
/*
* 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;
}
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
ヌル終了ではなく、最後に\n
(またはプラットフォームが使用しているものは何でも) 含まれています。ストリームでの次の I/O 操作の後、無効になります。