다른 유형에 함수 포인터 캐스팅
예를 들어, 이 함수에 의해,void (*)(void*)
콜백으로 사용하는 함수 포인터:
void do_stuff(void (*callback_fp)(void*), void* callback_arg);
다음과 같은 기능이 있는 경우:
void my_callback_function(struct my_struct* arg);
이거 안전하게 해도 돼요?
do_stuff((void (*)(void*)) &my_callback_function, NULL);
저는 이 질문을 살펴보았고 호환 함수 포인터에 캐스팅할 수 있다는 C 표준을 몇 가지 살펴보았습니다만, '호환 함수 포인터'가 무엇을 의미하는지 정의를 찾을 수 없습니다.
C 표준에 관한 한, 다른 유형의 함수 포인터에 함수 포인터를 던져 호출하면 정의되지 않은 동작입니다.부록 J.2 (정보)를 참조해 주세요.
이 동작은 다음과 같은 상황에서는 정의되지 않습니다.
- 포인터는 point-to 타입(6.3.2.3)과 호환되지 않는 타입의 함수를 호출하기 위해 사용됩니다.
섹션 6.3.2.3, 제8항은 다음과 같다.
한 유형의 기능에 대한 포인터는 다른 유형의 기능에 대한 포인터로 변환한 후 다시 변환할 수 있으며, 결과는 원래 포인터와 동일해야 한다.변환된 포인터를 사용하여 지정된 유형과 호환되지 않는 함수를 호출할 경우 동작은 정의되지 않습니다.
즉, 함수 포인터를 다른 함수 포인터 타입에 캐스트하고 다시 캐스트하여 호출할 수 있습니다.그러면 모든 것이 동작합니다.
호환성의 정의는 다소 복잡합니다.섹션 6.7.5.3 단락 15에서 확인할 수 있습니다.
두 가지 기능 유형이 호환되려면 양쪽 모두 호환 가능한127 반환 유형을 지정해야 한다.
또한 파라미터 유형 리스트가 모두 존재하는 경우 파라미터의 수와 생략부호 터미네이터의 사용에 일치해야 한다.해당 파라미터는 호환성이 있는 타입을 가져야 한다.한 유형이 매개변수 유형 목록을 가지고 있고 다른 유형이 함수 정의의 일부가 아닌 빈 식별자 목록을 포함하는 함수 선언자에 의해 지정되는 경우 매개변수 목록에는 생략 부호 종단자가 없어야 하며 각 매개변수의 유형은 디파우 적용에 따른 유형과 호환되어야 한다.인수 프로모션입니다.한 유형이 매개변수 유형 목록을 가지고 있고 다른 유형이 (공백일 가능성이 있는) 식별자 목록을 포함하는 함수 정의에 의해 지정된 경우, 두 유형은 매개변수 수에 동의해야 하며 각 프로토타입 매개변수의 유형은 기본 인수 승진을 적용함으로써 발생하는 유형과 호환되어야 한다.대응하는 식별자. (유형 호환성 및 복합유형의 결정에서는 함수 또는 배열유형으로 선언된 각 파라미터는 조정유형으로 간주되며, 수식유형으로 선언된 각 파라미터는 선언유형의 미적격버전을 갖는 것으로 간주됩니다.)
127) 두 기능 타입이 "old style"일 경우 파라미터 타입은 비교되지 않습니다.
두 유형이 호환되는지 여부를 판단하는 규칙은 섹션 6.2.7에 설명되어 있습니다.이러한 규칙은 다소 장황하기 때문에 여기서 인용하지는 않지만 C99 표준(PDF) 초안에서 읽을 수 있습니다.
여기서 관련 규칙은 섹션 6.7.5.1 단락 2에 있다.
두 가지 포인터 형식이 호환되려면 두 가지 포인터가 모두 동일하게 인정되어야 하며 두 가지 포인터가 호환 가능한 형식에 대한 포인터여야 한다.
그 때문에,void*
와 호환되지 않습니다.struct my_struct*
, 타입의 함수 포인터void (*)(void*)
유형의 함수 포인터와 호환되지 않습니다.void (*)(struct my_struct*)
따라서 이 함수 포인터의 캐스팅은 기술적으로 정의되지 않은 동작입니다.
그러나 실제로는 함수 포인터를 캐스팅하여 안전하게 빠져나갈 수 있습니다.x86 호출 규칙에서는 인수가 스택에 푸시되며 모든 포인터의 크기는 동일합니다(x86의 경우 4바이트 또는 x86_64의 경우 8바이트).함수 포인터를 호출하는 것은 스택상의 인수를 푸시하고 함수 포인터 타겟에 간접적으로 점프하는 것으로 요약됩니다.머신 코드 레벨에서는 타입에 대한 개념이 존재하지 않습니다.
절대 할 수 없는 일:
- 다른 호출 규약의 함수 포인터 간에 캐스트합니다.스택을 엉망으로 만들고, 기껏해야 크래시(crash), 최악의 경우 보안 구멍이 크게 뚫린 채 조용히 성공합니다.Windows 프로그래밍에서는 함수 포인터를 자주 전달합니다.Win32는 모든 콜백 함수가 다음을 사용하는 것을 상정하고 있습니다.
stdcall
호출 규약(매크로에서 사용)CALLBACK
,PASCAL
,그리고.WINAPI
모두 확장됩니다).표준 C 호출 규칙을 사용하는 함수 포인터를 전달한 경우(cdecl
)가 발생합니다. - C++에서는 클래스 멤버 함수 포인터와 일반 함수 포인터 사이에 캐스트합니다.이것은 종종 C++ 신입사원을 망친다.클래스 멤버 함수에 숨김이 있습니다.
this
파라미터, 그리고 멤버 함수를 일반 함수에 캐스팅하면this
사용하는 오브젝트, 그리고 다시 말해, 많은 나쁜 결과가 초래됩니다.
동작은 정의되지 않은 채 동작할 수 있는 또 다른 나쁜 생각:
- 기능 포인터와 일반 포인터 사이의 캐스팅(예: 캐스팅)
void (*)(void)
에 대해서void*
). 일부 아키텍처에서는 추가 컨텍스트 정보를 포함할 수 있으므로 함수 포인터의 크기가 일반 포인터와 같을 필요는 없습니다.이것은 x86에서는 정상적으로 동작하지만, 정의되지 않은 동작이라는 점에 주의해 주세요.
최근 GLIB의 어떤 코드에 대해서도 같은 문제로 문의했습니다.(GLIB는 GNOME 프로젝트의 핵심 라이브러리이며 C로 표기되어 있습니다.)모든 슬롯의 시그널 프레임워크가 그것에 달려있다고 들었습니다.
코드 전체에 걸쳐 유형 (1)부터 유형 (2)까지 수많은 주조 사례가 있습니다.
typedef int (*CompareFunc) (const void *a, const void *b)
typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)
다음과 같은 콜을 사용한 체인 스루에서는 일반적으로 사용됩니다.
int stuff_equal (GStuff *a,
GStuff *b,
CompareFunc compare_func)
{
return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}
int stuff_equal_with_data (GStuff *a,
GStuff *b,
CompareDataFunc compare_func,
void *user_data)
{
int result;
/* do some work here */
result = compare_func (data1, data2, user_data);
return result;
}
여기서 직접 확인하세요.g_array_sort()
: http://git.gnome.org/browse/glib/tree/glib/garray.c
위의 답변은 상세하고 정확할 수 있습니다.표준위원회에 참석하시면 됩니다.Adam과 Johannes는 잘 조사된 답변으로 칭찬받을 만하다.그러나 야외에서는 이 코드가 정상적으로 동작하고 있음을 알 수 있습니다.논란의 여지가 있나요?네. GLIB는 다양한 컴파일러/링커/커널로더(GCC/CLANG/MSVC)를 사용하여 다수의 플랫폼(Linux/Solaris/Windows/OS X)에서 컴파일/작업/테스트를 수행합니다.기준 따위는 엉망인 것 같아.
나는 이 대답들에 대해 잠시 생각했다.결론은 다음과 같습니다.
- 콜백 라이브러리를 쓰고 있는 경우는, 이것으로 문제가 없는 경우가 있습니다.경고 비우기 - 본인 부담으로 사용합니다.
- 그렇지 않으면 하지 마세요.
이 회신을 쓴 후 다시 생각해 보면, C 컴파일러의 코드도 같은 트릭을 사용해도 이상하지 않습니다.그리고 (대부분/모두?) 최신 C 컴파일러는 부트스트랩이 되어 있기 때문에 트릭이 안전하다는 것을 의미합니다.
조사해야 할 더 중요한 질문: 이 속임수가 효과가 없는 플랫폼/컴파일러/링커/로더를 찾을 수 있습니까?그건 중요한 브라우니 포인트야.일부 임베디드 프로세서/시스템이 마음에 들지 않을 것입니다.다만, 데스크탑 컴퓨팅(및 모바일/태블릿)에서는, 이 수법은 아직 유효할 가능성이 있습니다.
중요한 건 네가 할 수 있느냐 없느냐가 아니야.간단한 해결책은
void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);
좋은 컴파일러는 정말 필요한 경우에만 my_callback_helper의 코드를 생성합니다.이 경우 매우 유용합니다.
반환 유형과 매개 변수 유형이 호환되는 경우 호환되는 함수 유형이 있습니다(기본적으로 더 복잡함:).호환성은 "같은 타입"과 동일하며, 다른 타입을 사용할 수 있도록 느슨할 뿐이지만 "이 타입들은 거의 같다"는 표현도 있습니다.예를 들어, C89에서는 두 구조물이 동일하지만 이름만 다를 경우 호환성이 있었다.C99는 그것을 바꾼 것 같다.c 이론적 문서에서 인용(강력히 권장되는 판독치, btw!):
2개의 다른 변환 유닛 내의 구조체, 결합 또는 열거형 선언은 변환 유닛 자체가 분리되기 때문에 이들 선언의 텍스트가 같은 include 파일에서 온 경우에도 공식적으로 동일한 유형을 선언하지 않습니다.따라서 이 기준서는 그러한 유형의 추가 호환성 규칙을 규정하여 그러한 두 선언이 충분히 유사하다면 양립할 수 있도록 한다.
즉, yeah 엄밀히 말하면 이것은 정의되지 않은 동작입니다. 왜냐하면 do_stuff 함수 또는 다른 누군가가 다음을 가진 함수 포인터로 함수를 호출하기 때문입니다.void*
파라미터로 지정되지만 함수에 호환되지 않는 파라미터가 있습니다.그럼에도 불구하고 모든 컴파일러가 불평 없이 컴파일하여 실행하기를 기대합니다.하지만 다른 기능으로 더 깔끔하게 할 수 있습니다.void*
(및 콜백 함수로 등록) 그러면 실제 함수가 호출됩니다.
보이드 포인터는 다른 유형의 포인터와 호환됩니다.malloc과 mem이 어떻게 기능하는지의 중추입니다.memcpy
,memcmp
)가 동작합니다.일반적으로 C(C++가 아닌 C)로 동작합니다.NULL
다음과 같이 정의된 매크로입니다.((void *)0)
.
C99 의 6.3.2.3(항목 1)을 참조해 주세요.
보이드에 대한 포인터는 포인터에서 불완전한 유형 또는 객체 유형으로 변환할 수 있습니다.
C코드는 포인터 타입에 전혀 개의치 않는 명령어를 컴파일하기 때문에 당신이 말하는 코드를 사용해도 괜찮습니다.콜백 함수와 my_structure 이외의 포인터로 do_stuff를 실행하면 문제가 발생합니다.
동작하지 않는 것을 보여줌으로써 명확하게 할 수 있기를 바랍니다.
int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts
아니면...
void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts
기본적으로 런타임에 데이터가 계속 의미가 있는 한 원하는 포인터를 지정할 수 있습니다.
함수 호출이 C/C++로 동작하는 방법에 대해 생각해 보면, 이 함수는 스택상의 특정 항목을 푸시하고, 새로운 코드 위치로 점프하여 실행한 후 스택을 팝합니다.함수 포인터가 같은 반환 유형과 같은 인수 수/사이즈를 가진 함수를 기술하는 경우에는 문제가 없습니다.
그래서 당신은 안전하게 그렇게 할 수 있을 거라고 생각합니다.
언급URL : https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type
'programing' 카테고리의 다른 글
Vue js : Chrome에서 저장된 암호를 변경할 수 없습니다. (0) | 2022.06.30 |
---|---|
Java 리플렉션 퍼포먼스 (0) | 2022.06.30 |
버튼 클릭 시 Vue Transition이 트리거되지 않음 (0) | 2022.06.30 |
C++에서 CMake가 링커 언어를 판별할 수 없음 (0) | 2022.06.30 |
VueJ에서의 번호 포맷 방법s (0) | 2022.06.30 |