소스 코드의 끝에 정의를 작성할 때 C 언어로 데이터와 함수의 * 선언이 필요한 이유는 무엇입니까?


15

다음 "C"코드를 고려하십시오.

#include<stdio.h>
main()
{   
  printf("func:%d",Func_i());   
}

Func_i()
{
  int i=3;
  return i;
}

Func_i()는 소스 코드의 끝에 정의되며에서 사용하기 전에 선언이 제공되지 않습니다 main(). 컴파일러가 들어 왔을 때 , 그것은 컴파일러 Func_i()에서 나온다 . 컴파일러는 어떻게 든 반환 값을 찾아서 제공합니다 . 또한 컴파일러가의 반환 유형 을 찾을 수 없음을 알고 있습니다 . 그것은에 의해 디폴트는 (추측?) 걸리는 반환 형식 의 수하는을 . 코드에 코드가 있다면 컴파일러 는 다음과 같은 오류를 발생시킵니다 : Conflicting types for .main()main()Func_i()Func_i()printf()Func_i()Func_i()intfloat Func_i()Func_i()

위의 논의에서 우리는 다음을 볼 수 있습니다.

  1. 컴파일러는에서 반환 한 값을 찾을 수 있습니다 Func_i().

    • 컴파일러가 소스 코드에서 나와 소스 코드를 검색 Func_i()하여 반환 된 값을 찾을 수 있으면 명시 적으로 언급 된 main()Func_i () 유형을 찾을 수없는 이유는 무엇입니까 ?
  2. 컴파일러는 Func_i()float 유형 임을 알아야합니다 . 따라서 충돌하는 유형의 오류가 발생합니다.

  • 컴파일러가 그것이 Func_ifloat 유형 이라는 것을 알고 있다면 왜 여전히 Func_i()int 유형 이라고 가정 하고 충돌하는 유형의 오류를 제공합니까? 왜 Func_i()float 형식이되도록 강요하지 않습니까?

변수 선언에 대해서도 같은 의심이 있습니다. 다음 "C"코드를 고려하십시오.

#include<stdio.h>
main()
{
  /* [extern int Data_i;]--omitted the declaration */
  printf("func:%d and Var:%d",Func_i(),Data_i);
}

 Func_i()
{
  int i=3;
  return i;
}
int Data_i=4;

컴파일러는 'Data_i'undeclared (이 함수에서 처음 사용) 오류를 표시합니다.

  • 컴파일러가를 보면 Func_i()Func_ ()에 의해 반환 된 값을 찾기 위해 소스 코드로 내려갑니다. 컴파일러가 Data_i 변수에 대해 동일한 작업을 수행 할 수없는 이유는 무엇입니까?

편집하다:

컴파일러, 어셈블러, 프로세서 등의 내부 작업에 대한 세부 정보를 모릅니다. 내 질문의 기본 아이디어는 사용 후 마지막으로 소스 코드에서 함수의 반환 값을 말하면 (쓰기) "C"언어를 사용하면 컴퓨터가 오류없이 해당 값을 찾을 수 있습니다. 이제 컴퓨터가 왜 비슷한 유형을 찾을 수 없습니까? Func_i ()의 반환 값을 찾았으므로 Data_i 유형을 찾을 수없는 이유는 무엇입니까? extern data-type identifier;문을 사용하더라도 해당 식별자 (함수 / 변수)에서 값을 반환하도록 지시하지 않습니다. 컴퓨터가 해당 값을 찾을 수 있으면 왜 유형을 찾을 수 없습니까? 왜 전진 선언이 필요합니까?

감사합니다.


7
컴파일러는 Func_i에 의해 반환 된 값을 "찾지"않습니다. 이것은 실행 시간에 이루어집니다.
James McLeod

26
나는 공감하지는 않았지만 문제는 컴파일러의 작동 방식에 대한 심각한 오해에 기초하고 있으며 의견의 응답은 여전히 ​​극복해야 할 개념적 장애물이 있음을 시사합니다.
James McLeod

4
첫 번째 샘플 코드는 지난 15 년 동안 유효한 표준 규격 코드가 아닙니다. C99는 함수 정의에 리턴 유형이없고 내재 된 선언이 Func_i유효하지 않습니다. 정의되지 않은 변수를 암시 적으로 선언하는 규칙은 없었으므로 두 번째 조각은 항상 형식이 잘못되었습니다. (예, C89 / C90에서 느슨하면 유효하기 때문에 컴파일러는 여전히 첫 번째 샘플을 수락합니다.)
Jonathan Leffler

19
@ user31782 : 문제의 결론 : 언어 X는 왜 Y를 요구합니까? 그것이 디자이너가 선택한 선택이기 때문입니다. 당신은 가장 성공적인 언어 중 하나의 디자이너가 그들이 만든 맥락에서 그러한 선택을 이해하려고 노력하기보다는 수십 년 전에 다른 선택을했을 것이라고 주장하는 것 같습니다. 귀하의 질문에 대한 답변 : 왜 우리는 사전 신고가 필요합니까? 주어졌다 : C는 원 패스 컴파일러를 사용하고 있기 때문에. 대부분의 후속 질문에 대한 가장 간단한 대답 은 1 패스 컴파일러
Mr.Mindor

4
@ user31782 당신은 정말로 컴파일러와 프로세서가 실제로 어떻게 작동하는지 이해하기 위해 용의 책 을 읽고 싶어 합니다. 필요한 모든 지식을 단일 SO 답변 (또는 100에서)으로 증류하는 것은 불가능합니다. 컴파일러에 관심이있는 사람을위한 훌륭한 책.
Voo

답변:


26

C는 단일 패스 , 정적으로 유형이 있고 약한 유형 으로 컴파일 된 언어 이기 때문에 .

  1. 단일 패스 는 컴파일러가 함수 또는 변수의 정의를 미리 보지 않음을 의미합니다. 컴파일러는 미리 보지 않기 때문에 함수를 선언하기 전에 함수 선언이 이루어져야합니다. 그렇지 않으면 컴파일러가 형식 서명이 무엇인지 모릅니다. 그러나 함수의 정의는 나중에 같은 파일에서 또는 다른 파일에서 모두 가능합니다. 포인트 # 4를 참조하십시오.

    선언되지 않은 함수와 변수가 "int"유형 인 것으로 추정되는 역사적 아티팩트는 예외입니다. 현대의 관행은 항상 함수와 변수를 명시 적으로 선언하여 암시 적 타이핑을 피하는 것입니다.

  2. 정적으로 유형이 지정 되면 모든 유형 정보가 컴파일 타임에 계산됩니다. 그런 다음이 정보를 사용하여 런타임에 실행되는 기계 코드를 생성합니다. 런타임 타이핑의 C에는 개념이 없습니다. 일단 int, 항상 int, 일단 float, 항상 float입니다. 그러나 그 사실은 다음 점에서 다소 모호합니다.

  3. 약한 유형은 프로그래머가 명시 적으로 변환 작업을 지정하지 않아도 C 컴파일러가 숫자 유형간에 변환 할 코드를 자동으로 생성 함을 의미합니다. 정적 타이핑으로 인해 프로그램을 통해 매번 같은 방식으로 동일한 변환이 수행됩니다. 부동 소수점 값이 코드의 지정된 지점에서 정수 값으로 변환되면 부동 소수점 값은 항상 코드의 해당 지점에서 정수 값으로 변환됩니다. 런타임시 변경할 수 없습니다. 값 자체는 프로그램의 한 실행에서 다음 실행으로 변경 될 수 있으며 조건문은 어떤 코드 섹션이 어떤 순서로 실행되는지를 변경할 수 있지만 함수 호출이나 조건부없이 주어진 단일 코드 섹션은 항상 정확한 수행 실행될 때마다 동일한 작업.

  4. 컴파일 은 사람이 읽을 수있는 소스 코드를 분석하고이를 기계가 읽을 수있는 명령어로 변환하는 프로세스가 프로그램이 실행되기 전에 완전히 수행됨을 의미합니다. 컴파일러가 함수를 컴파일 할 때 주어진 소스 파일에서 어떤 문제가 발생하는지 알 수 없습니다. 그러나 컴파일 (및 어셈블리, 링크 등)이 완료되면 완성 된 실행 파일의 각 함수에는 실행될 때 호출 할 함수에 대한 숫자 포인터가 포함됩니다. 이것이 main ()이 소스 파일에서 함수를 추가로 호출 할 수있는 이유입니다. main ()이 실제로 실행될 때에는 Func_i ()의 주소에 대한 포인터가 포함됩니다.

    머신 코드는 매우 구체적입니다. 두 정수 (3 + 2)를 추가하는 코드는 두 부동 소수점 (3.0 + 2.0)을 추가하는 코드와 다릅니다. 그것들은 int를 float (3 + 2.0)에 추가하는 것과 다릅니다. 컴파일러는 함수의 모든 지점에 대해 해당 지점에서 수행해야하는 정확한 작업을 결정하고 해당 정확한 작업을 수행하는 코드를 생성합니다. 일단 완료되면 기능을 다시 컴파일하지 않고는 변경할 수 없습니다.

이러한 모든 개념을 종합하면, main ()이 Func_i ()의 유형을 결정하기 위해 더 이상 볼 수없는 이유는 컴파일 과정의 맨 처음에 유형 분석이 발생하기 때문입니다. 이 시점에서 main () 정의까지 소스 파일의 일부만 읽고 분석했으며 Func_i () 정의는 아직 컴파일러에 알려지지 않았습니다.

main ()이 Func_i ()가 호출 하는 위치를 "볼"수있는 이유는 컴파일이 이미 모든 식별자의 모든 이름과 유형을 해석 한 후 어셈블리가 이미 함수는 기계 코드에 연결되며 링크는 이미 호출 된 각 위치에 각 함수의 올바른 주소를 삽입했습니다.

물론 대부분의 까다로운 세부 사항은 생략했습니다. 실제 프로세스는 훨씬 더 복잡합니다. 귀하의 질문에 답변 할 수있는 고급 개요를 충분히 제공해 주시기 바랍니다.

또한 위에서 작성한 내용이 C에 구체적으로 적용됩니다.

다른 언어에서는 컴파일러가 소스 코드를 여러 번 통과 할 수 있으므로 컴파일러는 사전 선언없이 Func_i () 정의를 선택할 수 있습니다.

다른 언어에서, 함수 및 / 또는 변수는 동적으로 유형이 지정 될 수 있으므로 단일 변수가 보유하거나 단일 함수가 다른 시간에 정수, 부동 소수점, 문자열, 배열 또는 오브젝트를 전달하거나 리턴 할 수 있습니다.

다른 언어에서는 입력이 더 강력 할 수 있으므로 부동 소수점에서 정수로의 변환을 명시 적으로 지정해야합니다. 또 다른 언어에서는 타이핑이 약할 수 있으므로 문자열 "3.0"에서 float 3.0으로 변환하여 정수 3으로 변환하면 자동으로 수행됩니다.

그리고 다른 언어에서는 코드가 한 번에 한 줄씩 해석되거나 바이트 코드로 컴파일 된 다음 해석되거나 적시에 컴파일되거나 다양한 다른 실행 체계를 거치게됩니다.


1
올인원 답변 감사합니다. 너와 니키의 대답은 내가 알고 싶었던 것이다. 예 Func_()+1: 컴파일 타임에 컴파일러 Func_i() 적절한 머신 코드를 생성하기 위해 유형을 알아야합니다 . 어쩌면 런타임에 형식을 호출 하여 어셈블리를 처리 할 수 없거나 가능한Func_()+1 경우 프로그램이 런타임에 느려질 수 있습니다. 내 생각 엔 지금은 충분 해요
user106313

1
C의 암시 적으로 선언 된 함수의 중요한 세부 사항 : 함수는 유형으로 가정합니다 int func(...). 즉, 가변 인수 목록을 가져옵니다. 즉, 함수를 정의 int putc(char)했지만 선언하는 것을 잊어 버린 경우 대신 int putc(int)(가변 인수 목록을 통해 전달 된 char이로 승격되기 때문에) 호출됩니다 int. 따라서 OP의 예제가 서명이 암시 적 선언과 일치하여 작동하는 동안이 동작이 권장되지 않는 이유를 이해할 수 있습니다 (적절한 경고가 추가됨).
uliwitness

37

C 언어의 설계 제약 조건은 단일 패스 컴파일러로 컴파일해야했기 때문에 메모리 제약이 많은 시스템에 적합하다는 것입니다. 따라서 컴파일러는 이전에 언급 한 내용 만 알고 있습니다. 컴파일러는 소스에서 앞으로 건너 뛰어 함수 선언을 찾은 다음 해당 함수에 대한 호출을 컴파일하기 위해 돌아갈 수 없습니다. 따라서 모든 기호는 사용하기 전에 선언해야합니다. 다음과 같은 함수를 미리 선언 할 수 있습니다

int Func_i();

컴파일러를 돕기 위해 상단 또는 헤더 파일에.

예제에서는 피해야 할 C 언어의 두 가지 모호한 기능을 사용합니다.

  1. 함수가 올바르게 선언되기 전에 함수가 사용되면 "암시 적 선언"으로 사용됩니다. 컴파일러는 즉각적인 컨텍스트를 사용하여 함수 서명을 알아냅니다. 컴파일러는 나머지 코드를 스캔하여 실제 선언이 무엇인지 파악하지 않습니다.

  2. 유형이없는 것으로 선언 된 경우 유형은로 간주 int됩니다. 예를 들어 정적 변수 또는 함수 반환 유형의 경우입니다.

그래서 printf("func:%d",Func_i())우리는 암묵적인 선언을했습니다 int Func_i(). 컴파일러가 함수 정의에 도달 Func_i() { ... }하면 유형과 호환됩니다. 그러나이 float Func_i() { ... }시점에서 글 을 쓰면 암시성이 선언 int Func_i()되고 명시 적으로 선언 된 것 float Func_i()입니다. 두 선언이 일치하지 않기 때문에 컴파일러에서 오류가 발생합니다.

몇 가지 오해를 정리

  • 컴파일러가에서 반환 한 값을 찾지 못했습니다 Func_i. 명시 적 형식이 없으면 반환 형식이 int기본적으로 사용됩니다. 이 작업을 수행하더라도

    Func_i() {
        float f = 42.3;
        return f;
    }

    그런 다음 유형이 int Func_i()이고 반환 값이 자동으로 잘립니다!

  • 컴파일러는 결국의 실제 유형을 Func_i알지만 암시 적 선언 중에 실제 유형을 알지 못합니다. 나중에 실제 선언에 도달 할 때만 내재적으로 선언 된 유형이 올바른지 확인할 수 있습니다. 그러나이 시점에서 함수 호출에 대한 어셈블리가 이미 작성되었을 수 있으며 C 컴파일 모델에서 변경할 수 없습니다.


3
@ user31782 : 코드 순서는 컴파일 타임에 중요하지만 런타임에는 중요하지 않습니다. 프로그램이 실행될 때 컴파일러가 그림에서 벗어날 수 있습니다. 런타임에이 함수는 어셈블 및 링크되고 해당 주소는 해결되어 호출 주소 플레이스 홀더에 고정됩니다. (이보다 조금 더 복잡하지만 기본 아이디어입니다.) 프로세서는 앞뒤로 분기 할 수 있습니다.
Blrfl

20
@ user31782 : 컴파일러 값을 인쇄 하지 않습니다 . 컴파일러가 프로그램을 실행하지 않습니다 !!
Monica와의 가벼움 경주

1
@LightnessRacesinOrbit 나는 그것을 알고있다. 이름 프로세서를 잊어 버렸기 때문에 위의 설명에 실수로 컴파일러 를 작성했습니다 .
user106313

3
@Carcigenicate C는 B 언어의 영향을 많이 받았는데, B 언어는 포인터에 사용될 수있는 단어 너비 정수 숫자 형식의 단일 유형 만 가지고있었습니다. C는 원래이 동작을 복사했지만 C99 표준 이후 완전히 금지되었습니다. Unit형식 이론적 관점에서 좋은 기본 유형을 만들지 만 B와 C가 설계된 금속 시스템 프로그래밍에 가까운 실용성은 실패합니다.
amon

2
@ user31782 : 프로세서에 올바른 어셈블리를 생성하려면 컴파일러에서 변수 유형을 알아야합니다. 컴파일러는 암시를 발견하면 Func_i(), 그것은 즉시 생성하고 프로세서가 계속 다음 다음 다른 위치로 이동하려면 약간의 정수를 수신하고, 코드를 저장합니다. 컴파일러가 나중에 Func_i정의를 찾으면 서명이 일치하는지 확인하고 일치하는 경우 Func_i()해당 주소에 어셈블리를 배치하고 정수를 반환하도록 지시합니다. 프로그램을 실행할 때 프로세서는 값과 함께 해당 지시 사항을 따릅니다 3.
Mooing Duck

10

먼저, 프로그램은 C90 표준에 유효하지만 다음에 해당하는 것은 아닙니다. 암시 적 int (리턴 유형을 제공하지 않고 함수를 선언 할 수 있음) 및 함수의 암시 적 선언 (함수를 선언하지 않고 함수를 사용할 수 있음)은 더 이상 유효하지 않습니다.

둘째, 생각대로 작동하지 않습니다.

  1. C90에서는 결과 유형이 선택 사항이며 int결과를 의미하지 않습니다 . 변수 선언에서도 마찬가지입니다 (그러나 스토리지 클래스 static또는 을 제공해야합니다 extern).

  2. 컴파일러가 Func_i이전 선언없이 호출 된 것을 볼 때 , 선언이 있다고 가정합니다.

    extern int Func_i();

    코드에서 얼마나 효과적으로 Func_i선언 되는지 확인하지는 않습니다 . Func_i선언되거나 정의되지 않은 경우 컴파일러는 컴파일 할 때 동작을 변경하지 않습니다 main. 암시 적 선언은 함수만을위한 것이며 변수를위한 것은 없습니다.

    선언의 빈 매개 변수 목록이 함수가 매개 변수를 사용하지 않는다는 것을 의미하지는 않습니다 (지정해야 (void)함). 컴파일러가 매개 변수의 유형을 확인할 필요가 없으며 동일하다는 것을 의미합니다 가변 함수에 전달 된 인수에 적용되는 암시 적 변환


컴파일러가 main ()에서 나오고 소스 코드를 검색하여 Func_i ()에 의해 반환 된 값을 찾을 수 있다면, 명시 적으로 언급 된 Func_i ()의 유형을 찾을 수없는 이유는 무엇입니까?
user106313

1
@ user31782 Func_i의 이전 선언이없는 경우, Func_i가 호출 표현식에 사용 된 것을 볼 때 하나가있는 것처럼 동작 extern int Func_i()합니다. 어디에도 보이지 않습니다.
AProgrammer

1
@ user31782, 컴파일러는 아무데도 뛰어 오르지 않습니다. 해당 함수를 호출하는 코드를 내 보냅니다. 반환 된 값은 런타임에 결정됩니다. 동일한 컴파일 단위에 존재하는 간단한 함수의 경우 최적화 단계에서 함수를 인라인 할 수 있지만 언어 규칙을 고려할 때 고려해야 할 것은 아니지만 최적화입니다.
AProgrammer

10
@ user31782, 프로그램 작동 방식에 대한 심각한 오해가 있습니다. p.se가 그들을 수정하기에 좋은 곳이라고 생각하지 않기에 너무 진지합니다 (아마 채팅을하지만, 시도하지는 않을 것입니다).
AProgrammer

1
@ user31782 : 작은 스 니펫을 작성하고 컴파일하는 -S경우 (를 사용 gcc하는 경우) 컴파일러가 생성 한 어셈블리 코드를 볼 수 있습니다. 그런 다음 런타임에 반환 값을 처리하는 방법에 대한 아이디어를 얻을 수 있습니다 (일반적으로 프로세서 레지스터 또는 프로그램 스택의 일부 공간 사용).
Giorgio

7

당신은 코멘트에 썼다 :

실행은 라인 단위로 수행됩니다. Func_i ()에 의해 반환 된 값을 찾는 유일한 방법은 메인에서

그것은 오해입니다. 실행은 한 줄씩하지 않습니다. 컴파일 은 한 줄씩 수행되며 이름 확인은 컴파일 중에 수행되며 값만 반환하는 것이 아니라 이름 만 확인합니다.

유용한 개념 모델은 다음과 같습니다. 컴파일러가 행을 읽을 때 :

  printf("func:%d",Func_i());

다음과 같은 코드를 내 보냅니다.

  1. call "function #2" and put the return value on the stack
  2. put the constant string "func:%d" on the stack
  3. call "function #1"

또한 컴파일러는 function #2아직 선언되지 않은 함수 인 일부 내부 테이블 Func_i에 지정되지 않은 수의 인수를 가져 와서 int (기본값)를 반환하는 메모를 작성 합니다.

나중에 이것을 구문 분석 할 때 :

 int Func_i() { ...

컴파일러는 Func_i위에서 언급 한 표를 찾아 매개 변수와 리턴 유형이 일치하는지 확인합니다. 그렇지 않으면 오류 메시지와 함께 중지됩니다. 그럴 경우 현재 주소를 내부 함수 테이블에 추가하고 다음 행으로 넘어갑니다.

따라서 컴파일러는 Func_i첫 번째 참조를 구문 분석 할 때 "찾지"않았습니다 . 그것은 단순히 일부 테이블에 메모를 작성했으며 다음 줄을 파싱했습니다. 그리고 파일의 끝에는 오브젝트 파일과 점프 주소 목록이 있습니다.

나중에 링커는이 모든 것을 가져 와서 "함수 # 2"에 대한 모든 포인터를 실제 점프 주소로 바꿉니다.

  call 0x0001215 and put the result on the stack
  put constant ... on the stack
  call ...
...
[at offset 0x0001215 in the file, compiled result of Func_i]:
  put 3 on the stack
  return top of the stack

나중에 실행 파일이 실행될 때 점프 주소는 이미 확인되었으며 컴퓨터는 주소 0x1215로 바로 이동할 수 있습니다. 이름 조회가 필요하지 않습니다.

면책 조항 : 내가 말했듯이, 그것은 개념적 모델이며 실제 세계는 더 복잡합니다. 컴파일러와 링커는 오늘날 모든 종류의 미친 최적화를 수행합니다. 그들은 의심하기는하지만, 찾아보기 위해 "아래로 점프" 할 수도 있습니다Func_i . 그러나 C 언어는 그렇게 간단한 컴파일러를 작성할 있는 방식으로 정의됩니다 . 대부분의 경우 매우 유용한 모델입니다.


답변 주셔서 감사합니다. 컴파일러에서 코드를 생성 할 수 없습니다.1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313

1
(계속) 또한 : 만약 당신이 쓴다면 printf(..., Func_i()+1);-컴파일러 의 유형을 알아야하므로 , 컴파일러 명령을 Func_i내야하는지 add integer또는 add float명령을 내야하는지 결정할 수 있습니다 . 당신은 찾을 수있는 몇 가지 컴파일러가 타입 정보없이 갈 수있는 특별한 경우를하지만, 컴파일러를위한 작업에이 모든 경우.
니키

4
@ user31782 : 기계 명령어는 일반적으로 매우 간단합니다. 두 개의 32 비트 정수 레지스터를 추가하십시오. 16 비트 정수 레지스터에 메모리 주소를로드하십시오. 주소로 이동하십시오. 또한 유형 이 없습니다 : 32 비트 부동 소수점 숫자를 나타내는 메모리 위치를 32 비트 정수 레지스터에 행복하게로드하고 약간의 산술을 수행 할 수 있습니다. (거의 말이되지 않습니다.) 따라서 기계 코드를 직접 생성 할 수는 없습니다. 런타임 검사 및 스택의 추가 유형 데이터로 모든 작업을 수행하는 컴파일러를 작성할 수 있습니다. 그러나 C 컴파일러는 아닙니다.
니키

1
@ user31782 : IIRC에 따라 다릅니다. float값은 FPU 레지스터에있을 수 있습니다. 그러면 명령이 전혀 없습니다. 컴파일러는 컴파일 중에 어떤 레지스터에 어떤 값이 저장되어 있는지 추적하고 "FP 레지스터 X에 상수 1 추가"와 같은 항목을 내 보냅니다. 또는 사용 가능한 레지스터가없는 경우 스택에있을 수 있습니다. 그런 다음 "4에 의해 스택 포인터 증가"명령이 있으며 값은 "스택 포인터-4"와 같은 것으로 "참조"됩니다. 그러나 이러한 모든 것은 스택의 모든 변수 (이전 및 이후)의 크기가 컴파일 타임에 알려진 경우에만 작동합니다.
니키

1
모든 논의에서 나는이 이해에 도달했다. 컴파일러가 Func_i()or / and를 포함하는 문장에 대해 그럴듯한 어셈블리 코드를 만들려면 Data_i타입을 결정해야한다. 어셈블리 언어에서는 데이터 유형을 호출 할 수 없습니다. 안심하고 공부할 필요가 있습니다.
user106313

5

C와 선언을 요구하는 다른 많은 언어들은 프로세서 시간과 메모리가 비싸던 시대에 디자인되었습니다. C와 Unix의 개발은 꽤 오랫동안 진행되었고, 후자는 1979 년에 3BSD가 등장 할 때까지 가상 메모리를 가지고 있지 않았습니다. 여분의 작업 공간이 없다면 컴파일러는 단일 패스 업무가 아닌 경향이있었습니다. 전체 파일의 일부 표현을 메모리에 한 번에 유지하는 기능이 필요합니다.

싱글 패스 컴파일러는 우리와 마찬가지로 미래를 볼 수 없다는 안장에 사로 잡혀 있습니다. 이것은 그들이 확실하게 알 수있는 유일한 것은 코드 라인이 컴파일되기 전에 명시 적으로 들려 진 것입니다. Func_i()소스 파일에서 나중에 선언 되는 것은 분명 하지만 한 번에 작은 코드 덩어리에서 작동하는 컴파일러에는 아무런 힌트가 없습니다.

초기 C (AT & T, K & R, C89)에서 foo()선언 전에 함수 를 사용 하면 사실상 또는 암시 적으로 선언되었습니다 int foo(). 귀하의 예제 는 컴파일러가 귀하를 대신하여 선언 한 것과 일치하기 때문에 Func_i()선언 되었을 때 작동합니다 int. 다른 유형으로 변경하면 명시 적 선언없이 컴파일러가 선택한 것과 더 이상 일치하지 않기 때문에 충돌이 발생합니다. 이 동작은 선언되지 않은 함수 사용이 오류가 된 C99에서 제거되었습니다.

반환 유형은 어떻습니까?

대부분의 환경에서 객체 코드에 대한 호출 규칙은 호출되는 함수의 주소 만 알아야합니다. 이는 컴파일러와 링커가 처리하기가 비교적 쉽습니다. 실행은 함수의 시작 부분으로 건너 뛰고 반환 될 때 다시 나타납니다. 전달 인수와 반환 값의 다른 배열, 특히 호출 규칙 과 호출 배열은 호출자와 호출 수신자에 의해 결정됩니다 . 둘 다 동일한 규칙 세트를 공유하는 한, 프로그램은 해당 규칙을 공유하는 언어로 컴파일되었는지 여부에 관계없이 다른 오브젝트 파일에서 함수를 호출 할 수 있습니다. (과학 컴퓨팅에서는 FORTRAN을 호출하는 많은 C가 발생하고 그 반대의 경우도 마찬가지입니다.)

초기 C의 다른 특징 중 하나는 프로토 타입이 현재 존재하지 않는다는 것입니다. 함수의 반환 유형 (예 :)을 선언 할 수는 int foo()있지만 인수 int foo(int bar)는 아닙니다 (예 : 옵션이 아님). 위에서 설명한 것처럼 프로그램은 항상 인수에 의해 결정될 수있는 호출 규칙을 고수했기 때문에 존재했습니다. 잘못된 유형의 인수가있는 함수를 호출 한 경우, 이는 쓰레기 수거 상황입니다.

객체 코드에는 반환의 개념이 있지만 반환 유형은 없으므로 컴파일러는 반환 된 값을 처리하기 위해 반환 유형을 알아야합니다. 머신 인스트럭션을 실행할 때, 그것은 단지 비트 일 뿐이며 프로세서는 double실제로 비교하려고하는 메모리에 메모리가 있는지 여부를 신경 쓰지 않습니다 int. 그것은 단지 당신이 요구하는 것을 수행하며, 그것을 깨 뜨리면 두 조각을 모두 소유합니다.

다음 코드를 고려하십시오.

double foo();         double foo();
double x;             int x;
x = foo();            x = foo();

왼쪽의 코드는 호출로 컴파일 된 foo()다음 호출 / 반환 규칙을 통해 제공된 결과 x가 저장된 위치 로 복사 됩니다. 쉬운 사례입니다.

오른쪽의 코드는 형식 변환을 보여 주며 컴파일러가 함수의 반환 형식을 알아야하는 이유입니다. 부동 소수점 숫자는 메모리에 덤프 할 수 없으며, 다른 코드에서 발생할 수있는 int마술 변환이 없기 때문에 다른 코드에서 볼 수 있습니다. 최종 결과가 정수 여야하는 경우 저장하기 전에 프로세서가 변환을 수행하도록 안내하는 명령이 있어야합니다. 반환 유형을 foo()미리 알지 못하면 컴파일러는 변환 코드가 필요하다는 것을 알지 못합니다 .

멀티 패스 컴파일러는 변수, 함수 및 메소드를 처음 사용한 후에 선언 할 수있는 기능을 제공합니다. 이것은 컴파일러가 코드를 컴파일 할 때 이미 미래를 보았으며 무엇을 해야하는지 알고 있음을 의미합니다. 예를 들어, Java는 구문이 사용 후 선언을 허용한다는 사실 때문에 멀티 패스를 요구합니다.


답변 주셔서 감사합니다 (+1). 컴파일러, 어셈블러, 프로세서 등의 내부 작업에 대한 세부 정보를 모릅니다. 내 질문의 기본 아이디어는 사용 후 마지막으로 소스 코드에서 함수의 반환 값을 말하면 (쓰기) 이 기능을 사용하면 컴퓨터 에서 오류없이 해당 값을 찾을 수 있습니다. 이제 컴퓨터가 왜 비슷한 유형을 찾을 수 없습니까? 의 반환 값을 찾은 것으로 Data_i 유형 을 찾을 수없는 이유는 무엇입니까? Func_i()
user106313

나는 여전히 만족하지 않습니다. double foo(); int x; x = foo();단순히 오류를 제공합니다. 나는 우리가 이것을 할 수 없다는 것을 안다. 내 질문은 함수 호출에서 프로세서가 반환 값 만 찾습니다. 왜 리턴 타입도 찾을 수 없습니까?
user106313

1
@ user31782 : 안됩니다. 에 대한 프로토 타입이 foo()있으므로 컴파일러는 그에 대한 작업을 알고 있습니다.
Blrfl

2
@ user31782 : 프로세서에는 리턴 유형에 대한 개념이 없습니다.
Blrfl

1
@ user31782 컴파일 타임 질문 : 컴파일시이 모든 유형 분석을 수행 할 수있는 언어를 작성할 수 있습니다. C는 그런 언어가 아닙니다. C 컴파일러는 그것을 위해 설계되지 않았기 때문에 그것을 할 수 없습니다. 다르게 설계 되었습니까? 물론, 그렇게하려면 훨씬 더 많은 처리 능력과 메모리가 필요합니다. 결론은 그렇지 않았습니다. 오늘날의 컴퓨터가 가장 잘 처리 할 수있는 방식으로 설계되었습니다.
Mr.Mindor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.