C에서 인쇄()와 스캔()를 위해 매번 데이터 유형을 지정해야 하는 이유는?
아래 코드 조각에서 볼 수 있듯이, 나는 하나를 선언했다.char
변수와 하나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;
}
왜냐하면 다음과 같은 변수 인수 기능을 위한 휴대용 방법이 없기 때문이다.scanf
그리고printf
몇 개의 인수가 통과되었는지가 아니라 변수 인수의 유형을 아는 것.
C FAQ를 참조하십시오. 함수가 실제로 호출된 인수의 수를 확인하려면 어떻게 해야 하는가?
이것이 변수 인수의 수와 유형을 결정하기 위해 적어도 하나의 고정된 인수가 있어야 하는 이유다.그리고 이 주장(표준은 그것을 부른다.parmN
, 참조 C11(ISO/IEC 9899:201x) §7.16 변수 인수)는 이러한 특수 역할을 수행하며, 매크로에 전달된다.va_start
다시 수 . 다시 말해, 표준 C:에서와 같은 프로토타입으로는 기능을 가질 수 없다.
void foo(...);
컴파일러가 필요한 정보를 제공하지 못하는 이유는 단순히 컴파일러가 여기에 관여하지 않기 때문이다.함수의 프로토타입은 유형을 명시하지 않는다. 왜냐하면 이러한 함수의 유형은 가변형이기 때문이다.따라서 실제 데이터 유형은 컴파일 시간에 결정되는 것이 아니라 런타임에 결정된다.그런 다음 함수는 스택에서 한 개의 인수를, 다른 하나의 인수를 취한다.이 값들은 그것과 관련된 어떤 유형 정보도 가지고 있지 않기 때문에, 함수는 발신자가 제공한 정보인 형식 문자열을 사용하여 데이터를 해석하는 방법을 알고 있다.
함수 자체는 어떤 데이터 유형이 전달되는지 알 수 없고, 전달된 인수 횟수도 알 수 없기 때문에 어떤 데이터 유형이 전달되는지 알 수 없다.printf
스스로 결정할 수 있어
C++에서는 연산자 과부하를 사용할 수 있지만, 이것은 완전히 다른 메커니즘이다.왜냐하면 여기서 컴파일러는 데이터 유형과 사용 가능한 과부하 함수에 기초하여 적절한 함수를 선택하기 때문이다.
이것을 설명하자면printf
이것,이것,일요일면:
push value1
...
push valueN
push format_string
call _printf
그리고 의 원형printf
다음 사항:
int printf ( const char * format, ... );
그래서 형식 문자열에서 제공되는 것 외에는 이월된 형식 정보가 없다.
printf
본질적인 함수가 아니다.그것은 C언어의 일부가 아니다.컴파일러가 하는 일은 호출할 코드를 생성하는 것뿐입니다.printf
매개 변수 전달.이제 C는 런타임에 유형 정보를 파악하기 위한 메커니즘으로서 반성을 제공하지 않기 때문에 프로그래머는 필요한 정보를 명시적으로 제공해야 한다.
컴파일러는 스마트할 수 있지만 기능printf
또는scanf
어리석다 - 그들은 당신이 모든 통화에 대해 어떤 종류의 매개 변수를 전달하는지 모른다.이래서 합격해야 하는 거야%s
또는%d
매번
첫 번째 매개 변수는 형식 문자열이다.소수점 숫자를 인쇄하는 경우 다음과 같이 보일 수 있다.
"%d"
기호)"%5d"
(공백으로 폭 5까지 패딩된 번호)"%05d"
로 5 에 0으로 0로 5 까지 패딩된자)"%+d"
기호가 수치 가는 번호)"Value: %d\n"
(숫자 이전/이후의 일부 내용)
예를 들어, 형식 문자열에 포함할 수 있는 형식 지정 문자열을 보려면 위키백과의 형식 지정 자리 표시자를 참조하십시오.
또한 여기에는 두 개 이상의 매개 변수가 있을 수 있다.
"%s - %d"
그 그 열, 내용, 숫자)
컴파일러는 내가 변수를 선언했을 때 그것을 식별할 수 있을 만큼 성숙하지 않았는가?
아니요.
당신은 수십 년 전에 지정된 언어를 사용하고 있다.C에게 현대 디자인 미학을 기대하지 마라, 왜냐하면 그것은 현대 언어가 아니기 때문이다.현대 언어들은 사용적합성이나 명확성의 개선을 위해 편집, 해석 또는 실행에서 소량의 효율을 교환하는 경향이 있을 것이다.C는 컴퓨터 처리 시간이 비싸고 공급이 매우 제한적이었던 시대로부터 시작되었으며, 그 디자인은 이를 반영한다.
C와 C++가 빠르고 효율적이거나 금속과 가까이 있는 것에 정말로 신경을 쓸 때, 그것이 바로 C와 C++가 선택의 언어로 남아 있는 이유이기도 하다.
scanf
원형대로int scanf ( const char * format, ... );
매개변수 형식에 따라 주어진 데이터를 추가 인수가 가리키는 위치에 저장한다고 말한다.
그것은 컴파일러와 관련이 없으며, 그것은 모두 다음에 대해 정의된 구문에 관한 것이다.scanf
은 에 하다.모수 형식은 다음을 위해 필요하다.scanf
데이터를 입력하기 위해 예약할 크기에 대해 알고 있다.
GCC(및 다른 C 컴파일러)는 적어도 어떤 상황에서는 논쟁 유형을 추적한다.하지만 언어는 그렇게 설계되지 않았다.
그printf
함수는 변수 인수를 받아들이는 일반적인 함수다.가변적 인수는 어떤 종류의 런타임형식 식별 체계가 필요하지만, C 언어에서는 값이 런타임형 정보를 전혀 전달하지 않는다. (물론 C 프로그래머는 구조나 비트 조작 기법을 이용하여 런타임형 체계를 만들 수 있지만, 이것들은 언어에 통합되지 않는다.)
다음과 같은 기능을 개발할 때:
void foo(int a, int b, ...);
우리는 두 번째 논쟁 이후에 "임의" 수의 추가적인 논쟁들을 통과시킬 수 있고, 함수 통과 메커니즘의 바깥에 있는 어떤 종류의 프로토콜을 사용하여 얼마나 많은 논쟁들이 있는지 그리고 그들의 유형이 무엇인지 결정하는 것은 우리에게 달려 있다.
예를 들어, 우리가 이 기능을 다음과 같이 부르는 경우:
foo(1, 2, 3.0);
foo(1, 2, "abc");
탈주범이 사건을 구별할 수 있는 방법은 없어매개변수 통과 영역에는 약간의 비트가 있을 뿐이며, 그것들이 문자 데이터에 대한 포인터를 나타내는 것인지, 부동 소수점 번호를 나타내는 것인지 알 수 없다.
이러한 유형의 정보를 전달할 수 있는 가능성은 매우 많다.를 들어 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);
/*...*/
}
호출자가 null 포인터 터미네이터를 통과하지 못하면 기능이 계속 호출되기 때문에 동작이 정의되지 않는다.va_arg
모든 논쟁을 다 망친 후에우리의my_exec
함수는 다음과 같이 호출해야 한다.
my_exec("foo", "bar", "xyzzy", (char *) 0);
:0
할 수 있는 는 그 컴파일러는 해당 인수의 의도된 유형이 포인터 유형이라는 것을 알지 못한다.더 나아가(void *) 0
그것은 단순히 로서 통과될 것이기 때문에 정확하지 않다.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);
이것만이 기능을 구분할 수 있는 유일한 방법이기 때문이다(좋다).printf
scanf
되는 값 어떤 유형의 가치를 전달하고 있는지.예를 들어-
int main()
{
int i=22;
printf("%c",i);
return 0;
}
이 코드는 당신이 변수를 char로 처리하라고 프린트 f 함수에 말했으므로 정수 22가 아닌 문자를 출력할 것이다.
printf
그리고scanf
제어 문자열과 인수 목록을 수신하는 방식으로 설계되고 정의된 I/O 기능이다.
함수는 전달된 매개변수의 유형을 알지 못하며, 컴파일러도 이 정보를 전달하지 못한다.
왜냐하면 프린트F에서 데이터 유형을 지정하지 않고 데이터 형식을 지정하기 때문이다.이것은 어느 언어에서나 중요한 구별이며, C에서는 두 배로 중요하다.
를 사용하여 문자열을 스캔하는 경우%s
"내 문자열 변수에 대한 문자열 입력 파싱"을 말하는 것이 아니라C는 끈 타입이 없기 때문에 C에서는 그렇게 말할 수 없다.C가 문자열 변수에 가장 근접한 것은 문자열을 나타내는 문자를 포함하는 고정 크기 문자 배열이며 문자열의 끝은 null 문자로 표시된다.그래서 당신이 정말로 말하는 것은 "여기 끈을 잡을 수 있는 배열이 있다. 내가 당신이 파싱하기를 원하는 문자열 입력을 위해 충분히 클 것이라고 약속한다." 입니다.
원시?물론이야.C는 40년 전에 발명되었는데, 그 때 일반적인 기계는 RAM이 최대 64K나 되었다.이런 환경에서는 정교한 문자열 조작보다 RAM 보존이 우선이었다.
그래도 더.%s
스캐너는 문자열 데이터 유형이 있는 고급 프로그래밍 환경에서 유지된다.왜냐하면 타이핑이 아니라 스캔을 하기 때문이다.
'programing' 카테고리의 다른 글
서명되지 않은 정수 오버플로가 정의된 동작은 정의되지만 서명된 정수 오버플로는 정의되지 않는 이유는? (0) | 2022.05.02 |
---|---|
C에서 문자 숫자를 해당하는 정수로 변환 (0) | 2022.05.02 |
VueJs + Element UI: photo el 선택으로 기본값을 설정하는 방법? (0) | 2022.05.02 |
관찰자는 자바 9에서 더 이상 사용되지 않는다.그거 말고 뭘 쓸까? (0) | 2022.05.02 |
vue 구성 요소에서 링크를 사용하지 않도록 설정하는 방법 (0) | 2022.05.02 |