68

以下のコード スニペットからわかるように、1 つのchar変数と 1つの変数を宣言しintました。コードがコンパイルされると、変数strとのデータ型を識別する必要がありますi

%s変数をスキャンしているときに、または%dを指定して、それが文字列変数または整数変数であることを再度伝える必要があるのはなぜscanfですか? コンパイラは、変数を宣言したときにそれを識別するのに十分成熟していませんか?

#include <stdio.h>

int main ()
{
  char str [80];
  int i;

  printf ("Enter your family name: ");
  scanf ("%s",str);  
  printf ("Enter your age: ");
  scanf ("%d",&i);

  return 0;
}
4

11 に答える 11

122

可変引数関数のような移植可能な方法がないため、渡される引数の数でさえ、可変引数の型scanfprintf知ることができません。

C の FAQ を参照してください:関数が実際に呼び出された引数の数を確認するにはどうすればよいですか?


これが、可変引数の数と場合によっては型を決定するために、少なくとも 1 つの固定引数が必要な理由です。そして、この引数 (標準ではそれparmNを呼び出します。C11( ISO/IEC 9899:201x ) §7.16可変引数を参照) は、この特別な役割を果たし、マクロに渡されva_startます。つまり、標準 C では次のようなプロトタイプを持つ関数を作成することはできません。

void foo(...);
于 2013-08-13T08:05:53.433 に答える
28

The reason why the compiler can not provide the necessary information is simply, because the compiler is not involved here. The prototype of the functions doesn't specify the types, because these functions have variable types. So the actual data types are not determined at compile time, but at runtime. The function then takes one argument from the stack, after the other. These values don't have any type information associated with it, so the only way, the function knows how to interpret the data is, by using the caller provided information, which is the format string.

The functions themselves don't know which data types are passed in, nor do they know the number of arguments passed, so there is no way that printf can decide this on it's own.

In C++ you can use operator overloading, but this is an entire different mechanism. Because here the compiler chooses the appropriate function based on the datatypes and available overloaded function.

To illustrate this, printf, when compiled looks like this:

 push value1
 ...
 push valueN
 push format_string
 call _printf

And the prototype of printf is this:

int printf ( const char * format, ... );

So there is no type information carried over, except what is provided in the format string.

于 2013-08-13T08:23:55.777 に答える
14

printf組み込み関数ではありません。それ自体は C 言語の一部ではありません。コンパイラが行うのは、 を呼び出すコードを生成しprintf、パラメータを渡すことだけです。現在、C は実行時に型情報を把握するメカニズムとしてリフレクションを提供していないため、プログラマーは必要な情報を明示的に提供する必要があります。

于 2013-08-13T08:06:24.060 に答える
13

コンパイラーはスマートかもしれませんが、関数printfまたはscanf愚かです-呼び出しごとに渡すパラメーターのタイプが何であるかを知りません。%sこれが、%d毎回パスする必要がある理由です。

于 2013-08-13T08:00:15.163 に答える
10

最初のパラメータはフォーマット文字列です。10 進数を出力する場合は、次のようになります。

  • "%d"(10進数)
  • "%5d"(スペースで幅 5 にパディングされた 10 進数)
  • "%05d"(幅 5 までゼロで埋められた 10 進数)
  • "%+d"(10 進数、常に符号付き)
  • "Value: %d\n"(数字の前後に一部内容あり)

たとえば、ウィキペディアのフォーマット プレースホルダーを参照して、どのようなフォーマット文字列を含めることができるかを理解してください。

また、ここには複数のパラメーターが存在する場合があります。

"%s - %d"(文字列、コンテンツ、数値)

于 2013-08-13T08:06:45.657 に答える
8

コンパイラは、変数を宣言したときにそれを識別するのに十分なほど成熟していませんか?

いいえ。

何十年も前に指定された言語を使用しています。C は現代的な言語ではないため、C に現代的なデザインの美学を期待しないでください。現代の言語は、コンパイル、解釈、または実行におけるわずかな効率と引き換えに、使いやすさや明確さを向上させる傾向があります。Chail は、コンピュータの処理時間が高価で、供給が非常に限られていた時代のものであり、その設計はこれを反映しています。

また、高速、効率的、または金属に近いことを本当に、本当に気にかけているときに、C と C++ が選ばれ続ける理由でもあります。

于 2013-08-13T09:41:24.477 に答える
4

scanfプロトタイプint scanf ( const char * format, ... );が言うように、パラメーター形式に従って、指定されたデータを追加の引数が指す場所に格納します。

コンパイラとは関係ありません。すべてはscanf.Parameter format に定義された構文scanfに関するもので、入力するデータ用に予約するサイズを知るために必要です。

于 2013-08-13T08:05:18.453 に答える
4

GCC (およびおそらく他の C コンパイラ) は、少なくともいくつかの状況では、引数の型を追跡します。しかし、言語はそのように設計されていません。

このprintf関数は、可変引数を受け入れる通常の関数です。変数の引数には、ある種の実行時の型識別スキームが必要ですが、C 言語では、値は実行時の型情報を保持しません。(もちろん、C プログラマーは、構造体やビット操作のトリックを使用してランタイム型付けスキームを作成できますが、これらは言語に統合されていません。)

次のような関数を開発すると:

void foo(int a, int b, ...);

2 番目の引数の後に「任意の」数の追加の引数を渡すことができます。引数の数とその型を決定するのは、関数渡しメカニズムの外部にある何らかのプロトコルを使用することです。

たとえば、この関数を次のように呼び出すと:

foo(1, 2, 3.0);
foo(1, 2, "abc");

呼び出し先がケースを区別できる方法はありません。パラメータ受け渡し領域には数ビットしかなく、それらが文字データへのポインタを表しているのか、浮動小数点数を表しているのかわかりません。

この種の情報を伝達する可能性は数多くあります。たとえば、POSIX では、exec関数のファミリはすべて同じ型を持つ可変引数を使用char *し、null ポインターを使用してリストの終わりを示します。

#include <stdarg.h>

void my_exec(char *progname, ...)
{
  va_list variable_args;
  va_start (variable_args, progname);

  for (;;) {
     char *arg = va_arg(variable_args, char *);
     if (arg == 0)
       break;
     /* process arg */
  }

  va_end(variable_args);
  /*...*/
}

va_arg呼び出し元がヌル ポインター ターミネーターを渡すのを忘れた場合、関数はすべての引数を消費した後も呼び出しを続けるため、動作は未定義になります。my_exec関数は次のように呼び出す必要があります。

my_exec("foo", "bar", "xyzzy", (char *) 0);

0null ポインター定数として解釈されるコンテキストがないため、 のキャストが必要です。コンパイラーは、その引数の意図した型がポインター型であることを認識していません。さらに、 ではなく type(void *) 0として単純に渡されるため正しくありませんが、この 2 つはバイナリ レベルでほぼ確実に互換性があるため、実際には機能します。そのタイプの関数でよくある間違いは次のとおりです。void *char *exec

my_exec("foo", "bar", "xyzzy", NULL);

コンパイラはNULLたまたまキャスト0なしで定義されています。(void *)

別の考えられるスキームは、呼び出し元に、引数がいくつあるかを示す数値を渡すことを要求することです。もちろん、その数字は間違っている可能性があります。

の場合printf、書式文字列は引数リストを記述します。関数はそれを解析し、それに応じて引数を抽出します。

冒頭で述べたように、一部のコンパイラ、特に GNU C コンパイラは、コンパイル時にフォーマット文字列を解析し、引数の数と型に対して静的な型チェックを実行できます。

ただし、フォーマット文字列はリテラル以外の場合があり、実行時に計算される場合があることに注意してください。これは、そのような型チェック スキームの影響を受けません。架空の例:

char *fmt_string = message_lookup(current_language, message_code);

/* no type checking from gcc in this case: fmt_string could have
   four conversion specifiers, or ones not matching the types of
   arg1, arg2, arg3, without generating any diagnostic. */
snprintf(buffer, sizeof buffer, fmt_string, arg1, arg2, arg3);
于 2013-08-13T18:55:36.213 に答える