1

私はCに不慣れで、いくつかのことを学ぼうとしています。私がやろうとしているのは、ファイルを読み込んで情報を保存することです。フォーマットはCSVとなるため、各文字を読み込み、数字かカンマかを判断し、リンクリストに格納する予定です。私が抱えている問題は、次の例のように1文字以上の数字を読み取ることです。

5,2,24,5

これが私がこれまでに得たコードであり、それは私が期待する出力を返さないだけです。これがコードで、出力はコードサンプルの下にあります。

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

struct list {
  float value;
  struct list * next;
  struct list * prev;
};

int main( int argc, char *argv[] ){
  FILE *infile;
  char *token = NULL;
  char  my_char;

  /* Open the file. */
  // The file name should be in argv[1]
  if((infile = fopen(argv[1], "r")) == NULL) {
    printf("Error Opening File.\n");
    printf("ERROR: %s\n", strerror(errno));
    exit(1);
  }

  while((my_char = (char)fgetc(infile)) != EOF){
    //Is my_char a number?
    if(isdigit(my_char)){
      if(token == NULL){
        token = (char *)malloc(sizeof(char));
        memset(token, '\0', 1);
        strcpy(token, &my_char);
        printf("length of token -> %d\n", strlen(token));
        printf("%c\n", *token);
      } else {
        token = (char *)realloc(token, sizeof(token) + 1);
        strcat(token, &my_char);
        printf("%s\n", token);
      }
    }
  }

  free(token);
  fclose(infile);
}

そしてここに出力があります:

[estest@THEcomputer KernelFunctions]$ nvcc linear_kernel.cu -o linear_kernel.exe
[estest@THEcomputer KernelFunctions]$ ./linear_kernel.exe iris.csv
length of token -> 5
5
5a#1a#
5a#1a#3a#
5a#1a#3a#5a#
5a#1a#3a#5a#1a#
5a#1a#3a#5a#1a#4a#
*** glibc detected *** ./linear_kernel.exe: realloc(): invalid next size: 0x0000000001236350 ***

トークンの長さが1であると予想されるのに、なぜトークンの長さが「5」であり、5に続く奇妙な文字(「a#」で表される)であるのかわかりません。誰かが私がこれをもう少しよく理解するのを手伝ってもらえますか?

4

7 に答える 7

2
char *token = NULL;

token = (char *)realloc(token, sizeof(token) + 1);

tokenポインタです。 sizeofそれが指すメモリのチャンクの割り当てられたサイズを提供しません。ポインタオブジェクト自体のサイズがわかります。どうやら、ポインタはシステム上で4バイト(これは一般的です)なので、常に5バイトに再割り当てしています。

その他の提案:

exit(1);

exit(EXIT_FAILURE)よりポータブルです。

char my_char;

while((my_char = (char)fgetc(infile)) != EOF){

fgetccharではなくintを返します。値は、ファイルから読み取られた次の文字(unsigned charとして表され、intに変換されるため、通常は0..255の範囲)またはEOF(通常は-1)のいずれかです。システムでプレーン文字が署名されている場合、たまたま255の入力文字を使用すると、ループが途中で終了します。プレーン文字が符号なしの場合、の負の値をEOF符号付きの値に変換しているため、ループが終了しない可能性があります。後者の場合に何が起こるかは実際には100%わかりませんが、それは問題ではありません。my_charintを作成します。

token = (char *)malloc(sizeof(char));

の結果をキャストしないでくださいmalloc()。それは必要ではなく(暗黙的に変換できるようにをmalloc()返します)、エラーを隠すことができます。定義上1です。書くだけ:void*sizeof(char)

token = malloc(1);

そして、常に戻り値を確認してください。malloc()失敗するとNULLを返します。

memset(token, '\0', 1);

よりシンプル:*token = '\0';

1バイトを割り当ててから、一度にrealloc()1バイトを追加すると、非常に非効率になる可能性があります。

strcat(token, &my_char);

の2番目の引数はstrcat()、文字列へのポインタである必要があります。 &my_charは正しいタイプですが、my_charメモリ内の後続のバイトがたまたま' \0'、 。ではない場合Bad Things Can Happen

これは徹底的なレビューではありません。

推奨読書:comp.lang.cFAQ

于 2011-08-12T17:50:40.103 に答える
0

主な問題は、nullで終了する文字列の問題のようです。malloc呼び出しは1バイトを割り当てています。ただしstrcpy、ヌルターミネータ(ゼロバイト)に達するまでバイトをコピーします。したがって、後のバイトmy_charはスタックからの「ランダム」値であるため、結果は明確に定義されていません。

ヌルターミネータを使用できるようにするには、文字列の長さより1バイト長く(そして1バイト長く再割り当て)割り当てる必要があります。また、strcpyandstrcat呼び出しは、実際には単なる文字であるソース「文字列」に対しては無効です。実装している基本ロジックを引き続き使用するには、token配列内の適切な位置に文字値を割り当てるだけで済みます。または、2バイトの文字配列として宣言my_charし、2番目のバイトを0ターミネータに設定して、使用できるようstrcpystrcatすることもできます。例えば、

char my_char[2];
my_char[1] = '\0';

そして、それに応じて使用法を変更する必要がありますmy_char(値をに割り当て、strcpy / strcat呼び出しでmy_char[0]を削除します)。&コンパイラの警告/エラーは、これらの変更に対処するのに役立ちます。

于 2011-08-12T17:52:29.963 に答える
0

コード内の文字列に割り当てるデータは1バイトのみです。

token = (char *)malloc(sizeof(char));
memset(token, '\0', 1);

ただし、1バイトをゼロにするだけなので、文字列は必ずしもnullで終了するわけではありません。あなたが見ている可能性が最も高いのは、char*の後にメモリにあった余分なジャンクです。

于 2011-08-12T17:53:50.257 に答える
0

1つは、一度に1文字ずつ読むのではなく、一度に1行全体を読む方がはるかに簡単です。次に、を使用strtok()して行をコンマで分割できます。

コードにはいくつかの問題があります。

token = (char *)malloc(sizeof(char));

これにより、1バイトのみが割り当てられます。C文字列はnullで終了する必要があるため、長さが1の文字列でも、2バイトの割り当てられたスペースが必要です。

strcpy(token, &my_char);
strcat(token, &my_char);

my_charは単一の文字であり、nullで終了する文字列ではありません(これは何strcpy()であり、strcat()期待されています)。

sizeof(token)

これはあなたがやろうとしていることではありません。これにより、ポインタのサイズが返されます(これはのタイプですtoken。おそらくのようなものstrlen()が必要ですが、コードをリファクタリングして、単一文字ではなくnullで終了する文字列を使用していることを確認する必要があります。

于 2011-08-12T17:57:36.763 に答える
0

それが戻ってくるので、あなたはそうあるmy_charべきです。aを使用すると、EOF条件が見つからないことを意味します。intfgetcchar

int my_char;
/*...*/
while((my_char = fgetc(infile)) != EOF) {

EOF値はint有効ではありません。これにより、一度に1バイトずつ、細かいマニュアルcharからファイルを読み取りながら、ファイルの終わりを検出できます。

fgetc()によって返される整数値がchar型の変数に格納され、整数定数EOFと比較される場合、整数への拡張時のchar型の変数の符号拡張は実装定義であるため、比較は成功しない可能性があります。 。

他の人があなたの記憶エラーを指摘しているので、私はそれらを放っておこう。

于 2011-08-12T17:58:54.987 に答える
0
while((my_char = (char)fgetc(infile)) != EOF){

これは悪い時期です。 fgetcを返しますint。より多くの値を表すことができますcharEOF通常は-1です。に格納しているのでchar、どのようにキャラクターを表現することを期待します0xffか?あなたはしません。あなたはそれをとして扱うことになりますEOF。これを行う必要があります:

int c;

while ((c=fgetc(infile)) != EOF)
{
   char my_char = c;

次は...

       token = (char *)malloc(sizeof(char));

の戻り値を確認する必要がありmallocます。また、事前に必要以上に割り当てることを検討する必要があります。そうしないと、へのすべての呼び出しで、reallocこれまでに見た文字をコピーしなければならない可能性があります。たとえば、すべての割り当てサイズを2の累乗にすることで、アルゴリズムの複雑さが増します。また、C ++とは異なり、Cではからキャストする必要はありませんvoid*

       memset(token, '\0', 1);
       strcpy(token, &my_char);

これはあなたがそれが意味すると思うものではありません。 (&my_char)[1]これが機能するにはゼロでなければならないため、これは未定義の動作です。これを試してみてください:

token[0] = my_char;
token[1] = 0;

また、割り当てたのは1つだけcharです。これが機能するには2が必要です。

       token = (char *)realloc(token, sizeof(token) + 1);

sizeof前回割り当てた量を魔法のように覚えていません。指定されたタイプのコンパイル時のサイズのみが必要です。この場合はsizeof(char*)、32ビットシステムまたは64ビットシステムでそれぞれ4または8に相当します。変数の実際の割り当てサイズを追跡する必要があります。また、この種のrealloc障害は失敗時にメモリをリークする傾向があるため、次のようにする必要があります。

 void *ptr = realloc(token, new_length);
 if (!ptr) { /* TODO: handle error */ }
 token = ptr;

先に進む...

       strcat(token, &my_char);

&my_charこれは、C文字列であるかのように最後に使用したときと同じ未定義の動作をします。また、それが機能したとしてもstrcat、終わりを見つけるために文字列全体をトラバースする必要があるため、無駄です。

私の提案の要約は次のとおりです。

int c;
size_t alloc_size = 0;
size_t current_len = 0;
char *token = NULL;
void *ptr;

while ((c = fgetc(infile)) != EOF)
{
   if (is_digit(c))
   {
      if (alloc_size < current_len + 2)
      {
         if (!alloc_size)
         {
            // Set some arbitrary start size...
            //
            alloc_size = 64;
         }
         else
         {
            alloc_size *= 2;
         }

         if (!token)
            ptr = malloc(alloc_size);
         else
            ptr = realloc(token, alloc_size);

         if (!ptr)
         {
            free(token);
            return -1;
         }
      }

      token[current_len++] = c;
      token[current_len] = 0;
   }
}

/* TODO: do something with token... */

free(token);
于 2011-08-12T18:02:32.410 に答える
0

の実装strcpyは次のように簡単です。

while(*dest++ = *src++);

したがって、が指すメモリはsrc、少なくとも1つの「\0」文字で終わることが期待されます。あなたの場合、単一要素配列はnullではない文字を保持します。したがって、そのstrcpyメモリを超えて、そのセグメントの外側で逆参照することになり、障害が発生します。strcpy(buff, "abcd")コンパイラabcd\0がプログラムのコードセクションに配置するため、 likeの呼び出しが行われた場合、これは観察されません。

一般的にあなたの問題を解決するために、を使用することはそれを解決するためのより良くそしてより簡単な方法になるでしょうfgetlinestrtok

于 2011-08-13T02:20:37.077 に答える