source

이 호환되는 기능 타입들이 C에 있습니까?

manycodes 2023. 10. 23. 21:57
반응형

이 호환되는 기능 타입들이 C에 있습니까?

다음과 같은 C 프로그램을 생각해 봅니다.

int f() { return 9; }
int main() {
  int (*h1)(int);
  h1 = f; // why is this allowed?                                               
  return h1(7);
}

C11 표준 6.5.16.1항에 따르면, 간단한 과제에서 "다음 중 하나가 유지되어야 하며, 목록에서 관련된 것은 다음과 같습니다.

왼쪽 피연산자는 atomic, qualified 또는 unqualified pointer type을 가지고 있으며 (l값 변환 후 왼쪽 피연산자가 가질 유형을 consid링합니다) 두 피연산자는 호환되는 유형의 한정된 버전 또는 한정되지 않은 버전에 대한 포인터이며, 왼쪽이 가리키는 유형은 오른쪽이 가리키는 유형의 모든 한정자를 가지고 있습니다.

또한 이는 "제약 조건"입니다. 즉, 적합한 구현은 진단 메시지가 위반될 경우 이를 보고해야 합니다.

위 프로그램의 과제에서 이 제약을 위반한 것 같습니다.과제의 양쪽은 함수 포인터입니다.그렇다면 문제는 두 가지 기능 유형이 호환되는가 하는 것입니다.이는 6.7.6.3항에서 답하고 있습니다.

두 가지 함수 유형이 호환될 수 있도록 둘 다 호환 가능한 반환 유형을 지정해야 합니다.146) 또한 매개 변수 유형 목록이 둘 다 존재할 경우 매개 변수의 수와 타원 종단기의 사용에 일치해야 합니다. 해당 매개 변수는 호환 가능한 유형을 가져야 합니다.한 유형에 매개 변수 유형 목록이 있고 다른 유형에 함수 정의에 속하지 않고 빈 식별자 목록이 포함된 함수 선언기가 지정한 경우매개 변수 목록에는 타원형 종결자가 없어야 하며 각 매개 변수의 유형은 기본 인수 승격의 적용으로 발생하는 유형과 호환되어야 합니다.하나의 유형이 매개 변수 유형 목록을 가지고 있고 다른 유형이 (아마도 비어 있을) 식별자 목록을 포함하는 함수 정의에 의해 지정된 경우, 둘 다 매개 변수의 수에서 일치해야 합니다.그리고 각 프로토타입 파라미터의 유형은 해당 식별자의 유형에 기본 인수 승격을 적용한 결과로 발생하는 유형과 호환되어야 합니다.

이 경우 유형 중 하나인 h1에는 모수 유형 목록이 있고 다른 하나인 f에는 모수 유형 목록이 없습니다.따라서 위 인용문의 마지막 문장이 적용됩니다. 특히 "양쪽 모두 매개 변수의 수에 일치해야 합니다."분명히 h1은 하나의 파라미터를 사용합니다.f는?위의 바로 앞에 다음 사항이 발생합니다.

해당 함수 정의의 일부인 함수 선언기의 빈 목록은 함수에 매개 변수가 없음을 지정합니다.

따라서 분명히 0개의 파라미터를 사용합니다.따라서 두 유형은 매개 변수의 수가 일치하지 않고, 두 함수 유형이 호환되지 않으며, 할당이 제약 조건을 위반하여 진단을 발행해야 합니다.

그러나 gcc 4.8과 Clang 모두 프로그램을 컴파일할 때 경고를 표시하지 않습니다.

tmp$ gcc-mp-4.8 -std=c11 -Wall tmp4.c 
tmp$ cc -std=c11 -Wall tmp4.c 
tmp$

그런데 두 컴파일러 모두 f가 "int f(void)..."로 선언되면 경고를 주지만, 위의 표준을 읽은 제가 봤을 때는 그럴 필요가 없습니다.

질문:

Q1: 위 프로그램의 "h1=f;" 할당이 "양 피연산자 모두 호환되는 유형의 정규 버전 또는 비정규 버전에 대한 포인터"라는 제약 조건을 위반합니까?구체적으로:

Q2: 식 "h1=f"에서 h1의 유형은 일부 함수 유형 T1에 대해 T1에 대한 포인터입니다.T1이 정확히 무엇입니까?

Q3: 식 "h1=f"에서 f의 유형은 일부 함수 유형 T2에 대한 포인터-to-T2입니다.T2가 정확히 무엇입니까?

Q4: T1과 T2는 호환 가능한가요? (이에 대한 답변을 위해 표준의 적절한 부분이나 기타 문서를 인용해 주시기 바랍니다.)

Q1', Q2', Q3', Q4': 이제 f의 선언이 "int f(void) {return 9; }"로 변경되었다고 가정합니다.이 프로그램의 질문 1~4에 다시 답합니다.

다음 두 가지 결함 보고서를 통해 문제를 해결할 수 있습니다.

결함 보고서 316에 따르면 (앞으로 지뢰 강조):

6.7.5.3#15의 함수 유형 호환성에 대한 규칙은 함수 유형이 "(아마도 비어 있는) 식별자 목록을 포함하는 함수 정의에 의해 지정"되는 경우를 정의하지 않습니다. [...]

다음과 같은 예가 있습니다.

void f(a)int a;{}
void (*h)(int, int, int) = f;

그리고 다음과 같이 말합니다.

저는 표준의 의도가 단지 동일한 함수의 여러 선언의 호환성을 확인하기 위한 목적으로만 함수 정의에 의해 유형이 지정되는 것이라고 생각합니다. 여기서 함수의 이름이 식에 나타날 때, 함수의 유형은 반환 유형에 의해 결정되고 매개 변수 유형의 흔적이 포함되지 않습니다.그러나 구현 해석은 다릅니다.

질문 2: 위의 번역 단위가 유효합니까?

위원회의 답변은 다음과 같습니다.

위원회는 1분기와 2분기에 대한 답변이 '그렇다'고 믿고 있습니다.

이는 C99와 C11 사이에 이루어졌지만 위원회는 다음과 같이 덧붙입니다.

우리는 옛날 스타일의 규칙을 고칠 생각이 없습니다.그러나 이 문서에서 관찰한 내용은 대체로 정확한 것 같습니다.

C99와 C11은 당신이 질문에서 인용한 부분에서 크게 다르지 않다고 제가 말할 수 있습니다.결함 보고서 317을 좀 더 조사해 보면 다음과 같이 적혀 있습니다.

C의 의도는 빈 괄호가 있는 구 스타일 함수 정의가 나머지 번역 단위의 프로토타입을 포함한 함수 유형을 제공하지 않는다는 것이라고 생각합니다.예를 들어,

void f(){} 
void g(){if(0)f(1);}

질문 1: 이러한 함수 정의는 나머지 번역 단위에 대한 프로토타입을 포함하는 유형을 함수에 제공합니까?

질문 2: 위의 번역 단위가 유효합니까?

위원회의 답변은 다음과 같습니다.

1번 문항의 정답은 아니오이고, 2번 문항의 정답은 YES입니다.제약 조건 위반은 없지만 함수 호출이 실행되면 정의되지 않은 동작이 발생합니다.6.5.2.2;p6 참조.

이것은 함수 정의가 유형을 정의하는지 프로토타입을 정의하는지 여부가 과소 지정되어 있으므로 호환성 검사 요구 사항이 없다는 사실에 달려 있는 것으로 보입니다.이것은 원래 오래된 스타일의 함수 정의에 대한 의도였고 위원회는 아마도 그것이 더 이상 사용되지 않기 때문에 더 이상의 설명을 하지 않을 것입니다.

위원회는 번역 단위가 유효하다고 해서 미정의 행위가 없는 것은 아니라고 지적합니다.

역사적으로 C 컴파일러는 일반적으로 추가 인수가 무시되는 것을 보장하는 방식으로 인수 전달을 처리했으며, 프로그램이 실제로 사용되는 매개 변수에 대한 인수를 전달하도록 요구했습니다.

int foo(a,b) int a,b;
{
  if (a)
    printf("%d",b);
  else
    printf("Unspecified");
}

둘 중 하나를 통해 안전하게 전화를 걸 수 있는foo(1,123);아니면foo(0);, 후자의 경우에는 두 번째 인수를 지정할 필요가 없습니다.일반적인 호출 규약이 이러한 보증을 지원하지 않는 플랫폼(예: 클래식 매킨토시)에서도 C 컴파일러는 일반적으로 이를 지원하는 호출 규약을 사용하지 않습니다.

이 표준은 컴파일러가 이러한 사용을 지원할 필요가 없다는 것을 분명히 하지만, 이를 금지하기 위해 구현을 요구하는 것은 기존 코드를 깨뜨렸을 뿐만 아니라, 애플리케이션 코드가 다음과 같이 이전 표준 C에서 가능했던 것만큼 효율적이었던 코드를 구현하는 것을 불가능하게 만들었을 것입니다.o 쓸모없는 인수를 전달하도록 변경되며, 컴파일러가 코드를 생성해야 합니다.)이러한 사용법을 정의되지 않은 행동으로 만드는 것은 이를 지원해야 하는 모든 의무를 덜어주는 동시에 편리한 경우 구현이 이를 지원할 수 있도록 허용합니다.

질문에 대한 직접적인 답변은 아니지만 컴파일러는 함수를 호출하기 전에 값을 스택에 밀어 넣기 위한 어셈블리를 생성할 뿐입니다.

예를 들어(VS-2013 컴파일러 사용):

mov         esi,esp
push        7
call        dword ptr [h1]

이 함수에 로컬 변수를 추가하면 함수를 호출할 때마다 전달하는 값을 찾기 위해 해당 주소를 사용할 수 있습니다.

예를 들어(VS-2013 컴파일러 사용):

int f()
{
    int a = 0;
    int* p1 = &a + 4; // *p1 == 1
    int* p2 = &a + 5; // *p2 == 2
    int* p3 = &a + 6; // *p3 == 3
    return a;
}

int main()
{
    int(*h1)(int);
    h1 = f;
    return h1(1,2,3);
}

따라서 기본적으로 추가 인수를 사용하여 함수를 호출하는 것은 완전히 안전합니다. 프로그램 카운터가 함수의 주소로 설정되기 전에 단순히 스택에 밀어 넣었기 때문입니다(실행 가능 이미지의 코드 섹션에서).

물론 스택 오버플로우가 발생할 수 있다고 주장할 수 있지만, 이는 어떤 경우에도 발생할 수 있습니다(통과된 인수의 수가 선언된 인수의 수와 동일하더라도).

선언된 매개 변수가 없는 함수의 경우 컴파일러에 의해 매개 변수/매개 변수 유형이 유추되지 않습니다.다음 코드는 기본적으로 동일합니다.

int f()
{
    return 9;
}

int main()
{
    return f(7, 8, 9);
}

이것은 기본적으로 변수 길이 인수가 지원되는 방식과 관련이 있으며, ()는 기본적으로 (...)와 동일하다고 생각합니다.생성된 객체 코드를 자세히 살펴보면 f()의 인수는 함수를 호출하는 데 사용되는 레지스터에 여전히 푸시되지만 함수 정의에서 참조되므로 함수 내부에서 사용되지 않습니다.인수를 지원하지 않는 매개 변수를 선언하려면 다음과 같이 쓰는 것이 더 적합합니다.

int f(void)
{
    return 9;
}

int main()
{
    return f(7, 8, 9);
}

이 코드는 다음 오류로 인해 GCC에서 컴파일하지 못합니다.

In function 'main':
error: too many arguments to function 'f'

함수 선언 전에 __stdcall - 을 사용하려고 하면 컴파일이 되지 않습니다.
그 이유는 함수 호출이 기본적으로 __cdecl이기 때문입니다.호출자가 통화 후 스택을 삭제하는 것을 의미합니다.따라서 호출자 기능은 자신이 무엇을 밀어 넣었는지 알고 올바른 방식으로 스택을 클리어하기 때문에 원하는 모든 스택을 푸시할 수 있습니다.
__stdcall은 (다른 것들을 beside하는) 콜이 스택을 청소하는 것을 의미합니다.따라서 인수의 수가 일치해야 합니다.
... sign은 컴파일러에게 인수의 수가 달라진다고 말합니다.__stdcall로 선언하면 자동으로 __cdecl로 대체되며, 원하는 만큼의 인수를 사용할 수 있습니다.

이것이 컴파일러가 경고를 하지만 멈추지 않는 이유입니다.


오류: 스택이 손상되었습니다.

#include <stdio.h>

void __stdcall allmyvars(int num) {
    int *p = &num + 1;
    while (num--) {
        printf("%d ", *p);
        p++;
    }  
}

void main() {
    allmyvars(4, 1, 2, 3, 4);
}

작동하다

#include <stdio.h>

void allmyvars(int num) {
    int *p = &num + 1;
    while (num--) {
        printf("%d ", *p);
        p++;
    }  
}

void main() {
    allmyvars(4, 1, 2, 3, 4);
}

이 예제에서는 표준과 상호 연결되지 않은 정상적인 동작을 보여 줍니다.포인터를 함수로 선언하면 이 포인터를 할당한 후 암묵적 유형 변환으로 이어집니다.저는 왜 작동하는지 썼습니다.또한 당신은 글을 쓸 수 있습니다.

int main() {
  int *p;
  p = (int (*)(void))f; // why is this allowed?      
  ((int (*)())p)();
  return ((int (*)())p)(7);
}

그리고 그것은 여전히 표준의 일부이지만, 물론 표준의 다른 일부입니다.함수에 포인터를 int에 포인터로 할당해도 아무 일도 일어나지 않습니다.

언급URL : https://stackoverflow.com/questions/24743887/are-these-compatible-function-types-in-c

반응형