Why do I have to specify data type each time in C?

2019-01-21 06:06发布

问题:

As you can see from the code snippet below, I have declared one char variable and one int variable. When the code gets compiled, it must identify the data types of variables str and i.

Why do I need to tell again during scanning my variable that it's a string or integer variable by specifying %s or %d to scanf? Isn't the compiler mature enough to identify that when I declared my variables?

#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;
}

回答1:

Because there's no portable way for a variable argument functions like scanf and printf to know the types of the variable arguments, not even how many arguments are passed.

See C FAQ: How can I discover how many arguments a function was actually called with?


This is the reason there must be at least one fixed argument to determine the number, and maybe the types, of the variable arguments. And this argument (the standard calls it parmN, see C11(ISO/IEC 9899:201x) §7.16 Variable arguments ) plays this special role, and will be passed to the macro va_start. In another word, you can't have a function with a prototype like this in standard C:

void foo(...);


回答2:

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.



回答3:

Compiler may be smart, but functions printf or scanf are stupid - they do not know what is the type of the parameter do you pass for every call. This is why you need to pass %s or %d every time.



回答4:

printf is not an intrinsic function. It's not part of the C language per se. All the compiler does is generate code to call printf, passing whatever parameters. Now, because C does not provide reflection as a mechanism to figure out type information at run time, the programmer has to explicitly provide the needed info.



回答5:

The first parameter is a format string. If you're printing a decimal number, it may look like:

  • "%d" (decimal number)
  • "%5d" (decimal number padded to width 5 with spaces)
  • "%05d" (decimal number padded to width 5 with zeros)
  • "%+d" (decimal number, always with a sign)
  • "Value: %d\n" (some content before/after the number)

etc, see for example Format placeholders on Wikipedia to have an idea what format strings can contain.

Also there can be more than one parameter here:

"%s - %d" (a string, then some content, then a number)



回答6:

Isn't the compiler matured enough to identify that when I declared my variable?

No.

You're using a language specified decades ago. Don't expect modern design aesthetics from C, because it's not a modern language. Modern languages will tend to trade a small amount of efficiency in compilation, interpretation or execution for an improvement in usability or clarity. C hails from a time when computer processing time was expensive and in highly limited supply, and its design reflects this.

It's also why C and C++ remain the languages of choice when you really, really care about being fast, efficient or close to the metal.



回答7:

scanf as prototype int scanf ( const char * format, ... ); says stores given data according to the parameter format into the locations pointed by the additional arguments.

It is not related with compiler, it is all about syntax defined for scanf.Parameter format is required to let scanf know about the size to reserve for data to be entered.



回答8:

GCC (and possibly other C compilers) keep track of argument types, at least in some situations. But the language is not designed that way.

The printf function is an ordinary function which accepts variable arguments. Variable arguments require some kind of run-time-type identification scheme, but in the C language, values do not carry any run time type information. (Of course, C programmers can create run-time-typing schemes using structures or bit manipulation tricks, but these are not integrated into the language.)

When we develop a function like this:

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

we can pass "any" number of additional arguments after the second one, and it is up to us to determine how many there are and what are their types using some sort of protocol which is outside of the function passing mechanism.

For instance if we call this function like this:

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

there is no way that the callee can distinguish the cases. There are just some bits in a parameter passing area, and we have no idea whether they represent a pointer to character data or a floating point number.

The possibilities for communicating this type of information are numerous. For example in POSIX, the exec family of functions use variable arguments which have all the same type, char *, and a null pointer is used to indicate the end of the list:

#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);
  /*...*/
}

If the caller forgets to pass a null pointer terminator, the behavior will be undefined because the function will keep invoking va_arg after it has consumed all of the arguments. Our my_exec function has to be called like this:

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

The cast on the 0 is required because there is no context for it to be interpreted as a null pointer constant: the compiler has no idea that the intended type for that argument is a pointer type. Furthermore (void *) 0 isn't correct because it will simply be passed as the void * type and not char *, though the two are almost certainly compatible at the binary level so it will work in practice. A common mistake with that type of exec function is this:

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

where the compiler's NULL happens to be defined as 0 without any (void *) cast.

Another possible scheme is to require the caller to pass down a number which indicates how many arguments there are. Of course, that number could be incorrect.

In the case of printf, the format string describes the argument list. The function parses it and extracts the arguments accordingly.

As mentioned at the outset, some compilers, notably the GNU C Compiler, can parse format strings at compile time and perform static type checking against the number and types of arguments.

However, note that a format string can be other than a literal, and may be computed at run time, which is impervious to such type checking schemes. Fictitious example:

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


回答9:

It is because this is the only way to tell the functions (like printf scanf) that which type of value you are passing. for example-

int main()
{
    int i=22;
    printf("%c",i);
    return 0;
}

this code will print character not integer 22. because you have told the printf function to treat the variable as char.



回答10:

printf and scanf are I/O functions that are designed and defined in a way to receive a control string and a list of arguments.

The functions does not know the type of parameter passed to it , and Compiler also cant pass this information to it.



回答11:

Because in the printf you're not specifying data type, you're specifying data format. This is an important distinction in any language, and it's doubly important in C.

When you scan in a string with with %s, you're not saying "parse a string input for my string variable." You can't say that in C because C doesn't have a string type. The closest thing C has to a string variable is a fixed-size character array that happens to contain a characters representing a string, with the end of string indicated by a null character. So what you're really saying is "here's an array to hold the string, I promise it's big enough for the string input I want you to parse."

Primitive? Of course. C was invented over 40 years ago, when a typical machine had at most 64K of RAM. In such an environment, conserving RAM had a higher priority than sophisticated string manipulation.

Still, the %s scanner persists in more advanced programming environments, where there are string data types. Because it's about scanning, not typing.