C 표준에 관한 한, 함수 포인터를 다른 유형의 함수 포인터로 캐스팅 한 다음이를 호출하면 정의되지 않은 동작 입니다. 부록 J.2 (정보) 참조 :
다음 상황에서는 동작이 정의되지 않습니다.
- 포인터는 유형이 가리키는 유형 (6.3.2.3)과 호환되지 않는 함수를 호출하는 데 사용됩니다.
섹션 6.3.2.3, 단락 8은 다음과 같습니다.
한 유형의 함수에 대한 포인터는 다른 유형의 함수에 대한 포인터로 변환되고 다시 역으로 변환 될 수 있습니다. 결과는 원래 포인터와 동일하게 비교됩니다. 변환 된 포인터를 사용하여 형식이 가리키는 형식과 호환되지 않는 함수를 호출하면 동작이 정의되지 않습니다.
즉, 함수 포인터를 다른 함수 포인터 유형으로 캐스트하고 다시 캐스트하고 호출하면 모든 것이 작동합니다.
호환성 의 정의 는 다소 복잡합니다. 섹션 6.7.5.3, 단락 15에서 찾을 수 있습니다.
두 함수 유형이 호환 되려면 둘 다 호환 가능한 반환 유형 127을 지정해야 합니다.
또한 매개 변수 유형 목록이 둘 다있는 경우 매개 변수 수와 생략 부호 종결자를 사용하는 데 동의해야합니다. 해당 매개 변수는 호환 가능한 유형을 가져야합니다. 한 유형에 매개 변수 유형 목록이 있고 다른 유형이 함수 정의의 일부가 아니고 빈 식별자 목록을 포함하는 함수 선언자에 의해 지정된 경우 매개 변수 목록에는 줄임표 종결자가 없어야하며 각 매개 변수의 유형은 다음과 같아야합니다. 기본 인수 승격을 적용한 결과 유형과 호환되어야합니다. 한 유형에 매개 변수 유형 목록이 있고 다른 유형이 식별자 목록 (비어있을 수 있음)을 포함하는 함수 정의에 의해 지정되는 경우, 둘 다 매개 변수 수에 동의해야합니다. 각 프로토 타입 매개 변수의 유형은 기본 인수 승격을 해당 식별자 유형에 적용한 결과 유형과 호환되어야합니다. (유형 호환성 및 복합 유형의 결정에서 함수 또는 배열 유형으로 선언 된 각 매개 변수는 조정 된 유형을 갖는 것으로 간주되고 규정 된 유형으로 선언 된 각 매개 변수는 선언 된 유형의 규정되지 않은 버전을 갖는 것으로 간주됩니다.
127) 두 함수 유형이``이전 스타일 ''인 경우 매개 변수 유형은 비교되지 않습니다.
두 유형이 호환되는지 여부를 결정하는 규칙은 섹션 6.2.7에 설명되어 있으며 길이가 길기 때문에 여기에서 인용하지 않겠지 만 C99 표준 (PDF) 초안 에서 읽을 수 있습니다 .
여기서 관련 규칙은 섹션 6.7.5.1, 단락 2에 있습니다.
두 가지 포인터 유형이 호환 되려면 둘 다 동일하게 규정되어야하고 둘 다 호환 가능한 유형에 대한 포인터 여야합니다.
A는 이후 따라서, void*
호환되지 않는 A를 struct my_struct*
, 형의 함수 포인터 void (*)(void*)
타입의 함수 포인터와 호환되지 않습니다 void (*)(struct my_struct*)
함수 포인터의 캐스팅은 기술적으로 행동을 정의되지 않습니다 그래서.
그러나 실제로는 경우에 따라 함수 포인터를 캐스팅하여 안전하게 벗어날 수 있습니다. x86 호출 규칙에서 인수는 스택에 푸시되고 모든 포인터는 동일한 크기 (x86에서는 4 바이트, x86_64에서는 8 바이트)입니다. 함수 포인터를 호출하는 것은 스택의 인수를 푸시하고 함수 포인터 대상으로 간접 점프하는 것으로 요약되며 기계 코드 수준에서 유형에 대한 개념은 분명히 없습니다.
확실히 할 수없는 일 :
- 다른 호출 규칙의 함수 포인터간에 캐스트. 스택을 엉망으로 만들고 최악의 경우 크래시가 발생하고 거대한 보안 허점으로 조용히 성공할 수 있습니다. Windows 프로그래밍에서는 종종 함수 포인터를 전달합니다. Win32에서 모든 콜백 함수가 사용할 것으로 예상
stdcall
호출 규칙을 (이 매크로가 CALLBACK
, PASCAL
그리고 WINAPI
모두에 확장). 표준 C 호출 규칙 ( cdecl
) 을 사용하는 함수 포인터를 전달하면 불량이 발생합니다.
- C ++에서 클래스 멤버 함수 포인터와 일반 함수 포인터간에 캐스트합니다. 이것은 종종 C ++ 초보자를 괴롭 힙니다. 클래스 멤버 함수에는 숨겨진
this
매개 변수가 있으며 멤버 함수를 일반 함수로 캐스트하면 this
사용할 개체가 없으며 다시 많은 나쁜 결과가 발생합니다.
때로는 작동하지만 정의되지 않은 동작이기도 한 또 다른 나쁜 아이디어 :
- 함수 포인터와 일반 포인터 간의 캐스팅 (예 : a
void (*)(void)
를 a로 캐스팅 void*
). 일부 아키텍처에서는 추가 컨텍스트 정보를 포함 할 수 있기 때문에 함수 포인터가 반드시 일반 포인터와 같은 크기는 아닙니다. 이것은 아마도 x86에서 잘 작동하지만 정의되지 않은 동작임을 기억하십시오.