값을 반환하지 않고 비 공백 함수의 끝에서 벗어나는 이유가 컴파일러 오류를 발생시키지 않는 이유는 무엇입니까?


158

몇 년 전에 이것이 기본적으로 (최소한 GCC에서) 오류를 일으키지 않는다는 것을 깨달은 이래로 나는 왜 그 이유를 궁금해 했습니까?

컴파일러 플래그를 발행하여 경고를 생성 할 수 있지만 항상 오류가 아니어야한다는 것을 알고 있습니다. 무효가 아닌 함수가 값을 반환하지 않는 것이 왜 유효합니까?

주석에서 요청한 예 :

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

... 컴파일합니다.


9
또는 모든 경고를 오류와 같이 사소한 것으로 취급하고 필요한 모든 로컬 경고를 사용하여 가능한 모든 경고를 활성화합니다 (하지만 왜 코드에서 분명합니다).
Matthieu M.

8
-Werror=return-type해당 경고 만 오류로 취급합니다. 방금 경고를 무시하고 잘못된 this포인터를 추적하는 몇 분의 좌절 이 나를 여기로 이끌어 냈습니다.
jozxyqk

std::optional반환하지 않고 함수 의 끝에서 흘러 나오는 것은 "true"옵션을 반환 한다는 사실에 의해 악화됩니다.
Rufus

@Rufus 필요가 없습니다. 그것은 당신의 컴퓨터 / 컴파일러 / OS / 음력주기에서 일어난 일이었습니다. 정의되지 않은 동작으로 인해 컴파일러가 생성 한 정크 코드는 무엇이든 '참'옵션처럼 보입니다.
underscore_d

답변:


146

C99 및 C ++ 표준은 값을 반환하는 함수가 필요하지 않습니다. 값 반환 함수에서 누락 된 return 문은 함수 0에서만 정의됩니다 (return ) main.

이론적으로는 모든 코드 경로가 값을 반환하는지 확인하는 것이 매우 어렵고 임베디드 어셈블러 또는 기타 까다로운 방법으로 반환 값을 설정할 수 있다는 것이 포함됩니다.

에서 C ++ 11 초안 :

§ 6.6.3 / 2

함수 [...]의 끝에서 벗어나면 값 반환 함수에서 정의되지 않은 동작이 발생합니다.

§ 3.6.1 / 5

명령문이 main발생하지 않고 제어가 끝에 도달하면 return그 결과는 실행 효과입니다.

return 0;

C ++ 6.6.3 / 2에 설명 된 동작은 C에서 동일하지 않습니다.


gcc는 -Wreturn-type 옵션으로 호출하면 경고를 표시합니다.

-Wreturn-type 함수가 기본적으로 int로 리턴 유형으로 정의 될 때마다 경고합니다. 또한 리턴 유형이 void가 아닌 함수에서 리턴 값이없는 리턴 명령문에 대해 경고하고 (함수 본문의 끝에서 떨어지는 것이 값없이 리턴되는 것으로 간주 됨) 함수에 표현식이있는 리턴 명령문에 대해 경고합니다. 반환 유형이 무효입니다.

이 경고는 -Wall에 의해 활성화됩니다 .


호기심과 마찬가지로이 코드의 기능을 살펴보십시오.

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

이 코드는 공식적으로 정의되지 않은 동작을 가지며 실제로 는 컨벤션아키텍처에 따라 호출 됩니다. 하나의 특정 시스템에서 하나의 특정 컴파일러와 함께 리턴 값은 eax해당 시스템 프로세서 의 레지스터에 저장된 마지막 표현식 평가의 결과입니다 .


13
나는 정의되지 않은 행동을 "허용"이라고 부르는 것에주의를 기울일 것이지만, "금지"라고 부르는 것은 틀릴 수도있다. 오류가 아니며 진단이 필요하지 않은 것은 "허용"과는 다릅니다. 최소한, 당신의 대답은 당신이 옳다고 말하는 것처럼 조금 읽습니다.
궤도에서 가벼움 경주

4
@ Catskul, 왜 당신은 그 주장을 구입합니까? 사소하게 쉽지는 않지만 함수의 종료점을 식별하고 모두가 값 (및 선언 된 리턴 유형의 값)을 리턴하는지 확인하는 것이 실현 가능하지 않습니까?
BlueBomber

3
@Catskul, 예, 아니오 정적으로 유형이 지정된 언어 및 / 또는 컴파일 된 언어는 "엄청나게 비싸다"고 생각하는 많은 작업을 수행하지만 컴파일 시간에 한 번만 수행하므로 무시할만한 비용이 발생합니다. 그럼에도 불구하고, 함수의 종료점을 식별하는 것이 왜 슈퍼 선형이어야하는지는 알 수 없습니다. 함수의 AST를 통과하고 리턴 또는 종료 호출을 찾으십시오. 그것은 선형 시간이며 결정적으로 효율적입니다.
BlueBomber

3
@LightnessRacesinOrbit : 반환 값 함수가 때때로 값으로 즉시 반환하고 때로는 항상을 통해 종료 다른 함수가 호출하는 경우 throw또는 longjmp컴파일러는 도달 할 수없는 요구해야, return비 반환 함수 호출 다음? 필요하지 않은 경우는 흔하지 않으며, 그러한 경우에도 포함시켜야하는 요구 사항은 번거롭지 않았지만이를 요구하지 않는 결정은 합리적입니다.
supercat

1
@supercat : 그러한 경우 슈퍼 인텔리전트 컴파일러는 경고 나 오류를 일으키지 않지만 다시 말하지만 일반적인 경우에는 계산할 수 없으므로 일반적인 경험 법칙에 얽매이지 않습니다. 그러나 함수 끝까지 도달하지 못한다는 것을 알고 있다면 전통적인 함수 처리의 의미와는 거리가 멀기 때문에 그렇습니다. 계속 진행하여 안전하다는 것을 알 수 있습니다. 솔직히, 당신은 그 시점에서 C ++ 아래의 계층이므로 모든 보증은 어리 석습니다.
궤도에서 가벼움 레이스

42

gcc는 기본적으로 모든 코드 경로가 값을 반환하는지 검사하지 않습니다. 일반적으로이 작업은 수행 할 수 없기 때문입니다. 현재하고있는 일을 알고 있다고 가정합니다. 열거 형을 사용하는 일반적인 예를 고려하십시오.

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

프로그래머는 버그를 제외하고이 메소드가 항상 색상을 리턴한다는 것을 알고 있습니다. gcc는 당신이하고있는 일을 알고 있다고 확신하므로 함수의 맨 아래에 반환을 강요하지는 않습니다.

반면에 javac는 모든 코드 경로가 값을 리턴하는지 확인하고 모두 수행 할 수없는 경우 오류를 발생시킵니다. 이 오류는 Java 언어 사양에서 요구합니다. 때때로 그것은 잘못되어 불필요한 반환 진술서를 작성해야합니다.

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

철학적 차이입니다. C 및 C ++는 Java 또는 C #보다 더 관용적이고 신뢰할 수있는 언어이므로 새로운 언어의 일부 오류는 C / C ++의 경고이며 일부 경고는 기본적으로 무시되거나 해제됩니다.


2
javac가 실제로 코드 경로를 확인하면 그 지점에 도달 할 수 없다는 것을 알 수 없습니까?
Chris Lutz

3
첫 번째 사례에서는 모든 열거 형 사례 (기본 사례 또는 전환 후 반품 필요)를 다루는 데 대한 크레딧을 제공하지 않으며 두 번째 사례에서는 System.exit()결코 반환 되지 않는다는 것을 알지 못합니다 .
John Kugelman

2
javac (그렇지 않은 강력한 컴파일러)가 System.exit()결코 리턴하지 않는 것을 아는 것은 간단 해 보입니다 . 나는 그것을 찾았다 ( java.sun.com/j2se/1.4.2/docs/api/java/lang/… ), 문서는 단지 "보통 반환하지 않는다"고 말합니다. 그게 무슨 뜻인지 궁금합니다.
Paul Biggar

@Paul : 훌륭한 편집자가 없었 음을 의미합니다. 다른 모든 언어는 "정상적으로 리턴하지 않음", 즉 "정상 리턴 메커니즘을 사용하여 리턴하지 않습니다"라고 말합니다.
Max Lybbert

1
누군가가 열거 형에 새로운 값을 추가하면 논리의 정확성이 깨질 수 있기 때문에 적어도 첫 번째 예제가 발생하면 적어도 경고 한 컴파일러를 선호합니다. 큰 소리로 불평하거나 충돌하는 기본 사례를 원합니다 (어쩌면 어설 션 사용).
Injektilo

14

당신은 왜 값 반환 함수의 끝에서 흘러 나오는가 (즉, 명시 적없이 종료 return하는 것) 오류가 아닌가?

첫째, C에서 함수가 의미있는 것을 반환하는지 여부는 실행 코드가 실제로 반환 된 값을 사용할 때만 중요 합니다. 어쨌든 대부분의 언어를 사용하지 않을 것이라는 것을 알고있을 때, 언어는 당신이 어떤 것을 반환하도록 강요하지 않았을 수도 있습니다.

둘째, 분명히 언어 사양은 컴파일러 작성자가 명시 적 존재에 대해 가능한 모든 제어 경로를 감지하고 확인하도록 강요하고 싶지 않았습니다 return(많은 경우에는 그렇게하기가 어렵지는 않습니다). 또한 일부 제어 경로는 반환되지 않는 함수 ( 일반적으로 컴파일러에 알려지지 않은 특성) 이어질 수 있습니다 . 이러한 경로는 성가신 잘못된 긍정의 원천이 될 수 있습니다.

또한 C와 C ++는이 경우 동작에 대한 정의가 다릅니다. C ++에서 값 반환 함수의 끝에서 흘러 나가는 것은 호출 코드에서 함수의 결과를 사용하는지 여부에 관계없이 항상 정의되지 않은 동작입니다. C에서는 호출 코드가 반환 된 값을 사용하려고하는 경우에만 정의되지 않은 동작이 발생합니다.


+1이지만 C ++ return은 끝에서 명령문을 생략 할 수 없습니다 main().
Chris Lutz

3
@Chris Lutz : 그렇습니다 main.
AnT

5

C / C ++에서는 무언가를 리턴한다고 주장하는 함수에서 리턴하지 않는 것이 합법입니다. calling exit(-1)또는 호출하거나 예외를 발생시키는 함수 와 같은 여러 유스 케이스가 있습니다 .

컴파일러는 요청하지 않으면 UB로 이어 지더라도 유효한 C ++을 거부하지 않습니다. 특히 경고 가 생성 되지 않도록 요청 하고 있습니다. (Gcc는 여전히 기본적으로 일부를 켜고 있지만 추가하면 이전 기능에 대한 새로운 경고가 아닌 새로운 기능과 일치하는 것처럼 보입니다)

일부 경고를 내도록 기본 인수 없음 gcc를 변경하면 기존 스크립트를 변경하거나 시스템을 변경할 수 있습니다. 잘 설계 -Wall되고 경고를 처리하거나 개별 경고를 토글합니다.

C ++ 툴 체인 사용을 배우는 것은 C ++ 프로그래머가되는 것을 배우는 데 장애가되지만, C ++ 툴 체인은 일반적으로 전문가가 작성합니다.


그래, Makefile나는 그것을 실행하고 -Wall -Wpedantic -Werror있지만, 이것은 인수를 제공하는 것을 잊어 버린 일회성 테스트 스크립트였습니다.
기금 모니카의 소송

2
예를 들어, GCC 부트 스트랩의 -Wduplicated-cond일부가되었습니다 -Wall. 대부분의 코드에 적합 해 보이는 일부 경고 는 모든 코드에 적합하지 않습니다. 이것이 기본적으로 활성화되어 있지 않은 이유입니다.
어, 누군가는 강아지가 필요해

첫 번째 문장은 "Flowing off .... undefined behaviour ..."라는 대답에 인용 된 내용과 모순되는 것 같습니다. 아니면 ub는 "법적"으로 간주됩니까? 아니면 반환되지 않은 값이 실제로 사용되지 않는 한 UB가 아님을 의미합니까? 나는 C ++ 사례 btw에 대해 걱정하고있다
idclev 463035818

@ tobi303 int foo() { exit(-1); }int"int를 반환하도록 요청하는"함수에서 반환되지 않습니다 . 이것은 C ++에서 합법적입니다. 이제 아무 것도 반환하지 않습니다 . 그 기능의 끝에 도달하지 못했습니다. 실제로 끝에 도달 하는 foo것은 정의되지 않은 동작입니다. 프로세스 사례의 끝을 무시 하면서도 int foo() { throw 3.14; }돌아올 것을 주장 int하지만 결코 그렇지 않습니다.
Yakk-Adam Nevraumont

1
그래서 void* foo(void* arg) { pthread_exit(NULL); }같은 이유로 괜찮을 것 같아요 (유일한 사용을 통해 pthread_create(...,...,foo,...);)
idclev 463035818

1

나는 이것이 레거시 코드 때문이라고 생각합니다 (C는 C + +와 같은 리턴 문을 요구하지 않았습니다). 아마도 그 "기능"에 의존하는 거대한 코드 기반이있을 것입니다. 그러나 적어도 -Werror=return-type 많은 컴파일러 (gcc 및 clang 포함) 에는 플래그가 있습니다.


1

일부 제한적이고 드문 경우에, 값을 반환하지 않고 비 공백 함수의 끝에서 흘러 나오는 것이 유용 할 수 있습니다. 다음 MSVC 특정 코드와 같습니다.

double pi()
{
    __asm fldpi
}

이 함수는 x86 어셈블리를 사용하여 pi를 반환합니다. GCC의 어셈블리와 달리 return결과에 오버 헤드가 발생하지 않고이 작업을 수행 하는 방법을 알지 못합니다 .

내가 아는 한, 주류 C ++ 컴파일러는 분명히 유효하지 않은 코드에 대해 최소한 경고를 표시해야합니다. 본문을 pi()비워두면 GCC / Clang이 경고를보고하고 MSVC가 오류를보고합니다.

사람들은 예외와 exit대답에 대해 언급했습니다 . 그 이유는 유효하지 않습니다. 어느 예외를 발생, 또는 전화 exit, 것 없는 함수 실행이 끝을 흐름합니다. 그리고 컴파일러는 그것을 알고 있습니다 : throw 문을 작성하거나 exit빈 본문을 호출 pi()하면 컴파일러의 경고 또는 오류가 중지됩니다.


MSVC는 특히 void인라인 asm이 반환 값 레지스터에 값을 남겨둔 후 비 기능 종료를 방지합니다 . ( st0이 경우 x87 , 정수의 경우 EAX. st0 대신 xmm0에서 float / double을 반환하는 호출 규칙의 경우 xmm0 일 수 있음). 이 동작을 정의하는 것은 MSVC에만 해당됩니다. -fasm-blocks동일한 구문을 지원하기 위해 함께 사용하지 않아도 안전합니다. __asm ​​{}을
Peter Cordes

0

어떤 상황에서 오류가 발생하지 않습니까? 반환 유형을 선언하고 무언가를 반환하지 않으면 나에게 오류처럼 들립니다.

내가 생각할 수있는 한 가지 예외는 main()함수이며, return명령문 이 전혀 필요하지 않습니다 (적어도 C ++에서는; C 표준 중 어느 것도 편리하지 않습니다). 리턴이 없으면 return 0;마지막 명령문 인 것처럼 작동 합니다.


1
main()returnC에서 필요
크리스 루츠

@Jefromi : OP는 return <value>;성명서 없이 무효 기능을 요구하고 있습니다
Bill

2
main은 C와 C ++에서 자동으로 0을 반환합니다. C89는 명시적인 수익이 필요합니다.
Johannes Schaub-litb

1
@ 크리스 : 암시를 거기 C99에서 return 0;의 끝에서 main()(그리고 main()에만 해당). 그러나 return 0;어쨌든 추가하는 것이 좋습니다 .
pmg

0

컴파일러 경고를 설정 해야하는 것처럼 들립니다.

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function main’:
<stdin>:1: warning: return with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$

5
"켜기-오류"는 대답이 아닙니다. 분명히 경고와 오류로 분류 된 이슈들 사이에 심각도 차이가 있으며 gcc는 이것을 덜 덜 심각한 클래스로 취급합니다.
Cascabel

2
@Jefromi : 순수한 언어의 관점에서 경고와 오류 사이에는 차이가 없습니다. 컴파일러는 "무시 메시지"를 발행하기 만하면됩니다. 컴파일을 중지하거나 "오류"및 "경고"를 호출 할 필요는 없습니다. 진단 메시지가 발행 된 경우 (또는 모든 종류) 결정을 내리는 것은 전적으로 귀하의 책임입니다.
AnT

1
그런 다음 해당 문제로 인해 UB가 발생합니다. 컴파일러는 UB를 전혀 잡을 필요가 없습니다.
AnT

1
n1256의 6.9.1 / 12에서 "함수를 종료하는}에 도달하고 호출자가 함수 호출 값을 사용하면 동작이 정의되지 않습니다"라고 표시됩니다.
Johannes Schaub-litb

2
@Chris Lutz : 보지 못했습니다. 비 공백 함수에서 명시 적 공백 을 사용하는 return;것은 제한 조건 위반 return <value>;이며, void 함수에서 사용하는 것은 제한 조건 위반 입니다. 그러나 그것은 주제가 아니라고 생각합니다. 내가 이해 return했듯이 OP는 ( 공통이 함수 의 끝에서 흐르도록 허용 하지 않고) 무효가 아닌 함수를 종료하는 것 입니다. AFAIK 제약 조건 위반이 아닙니다. 표준은 단지 C ++에서는 항상 UB이고 때로는 C에서는 UB라고 말합니다.
AnT

-2

c99에서는 제한 조건 위반이지만 c89에서는 제한되지 않습니다. 대조:

c89 :

3.6.6.4 return진술

제약

return반환 유형이 인 함수에는 표현식이 포함 된 명령문이 나타나지 않습니다 void.

c99 :

6.8.6.4 return진술

제약

return반환 유형이 인 함수에는 표현식이 포함 된 명령문이 나타나지 않습니다 void. return표현이없는 문은 그 반환 타입 함수에 표시하여야한다 void.

심지어에서 --std=c99모드, GCC는 (비록 추가 가능하지 않고 경고를 발생합니다 -W기본적으로 필요하며, 플래그 또는 C89 / 90).

c89에서 " }함수를 종료하는 return것은 표현식없이 명령문 을 실행하는 것과 같습니다 "(3.6.6.4) 에 추가하도록 편집합니다 . 그러나 c99에서는 동작이 정의되지 않습니다 (6.9.1).


4
여기에는 명시 적 return진술 만 포함 됩니다. 값을 반환하지 않고 함수의 끝에서 떨어지는 것을 다루지 않습니다.
John Kugelman

3
C99에서 "함수를 종료하는}에 도달하는 것은 표현식없이 return 문을 실행하는 것과 같습니다"라는 제약 조건 위반이 아니므로 진단이 필요하지 않습니다.
Johannes Schaub-litb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.