문제

가 하는 기능 허용 void (*)(void*) 함수 포인터로 사용하기 위해 callback:

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 (정보)를 참조하십시오.

동작은 다음과 같은 상황에서 정의되지 않습니다.

  • 포인터는 유형이 포인트-투 타입 (6.3.2.3)과 호환되지 않는 함수를 호출하는 데 사용됩니다.

섹션 6.3.2.3, 8 항 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 또는 8 바이트 x86_64)입니다. 함수 포인터를 호출하는 것은 스택의 인수를 푸시하고 함수 포인터 대상으로 간접적으로 점프하는 것으로 요약되며 기계 코드 레벨에서 유형의 개념은 분명히 없습니다.

당신이 확실히 캔트 하다:

  • 다른 호출 규칙의 기능 포인터 사이를 캐스트합니다. 당신은 스택을 엉망으로 만들고, 최선의 충돌은 최악의 경우 큰 격차 보안 구멍으로 조용히 성공할 것입니다. Windows 프로그래밍에서는 종종 기능 포인터를 통과합니다. Win32는 모든 콜백 함수가 사용될 것으로 기대합니다 stdcall 컨벤션 (매크로 CALLBACK, PASCAL, 그리고 WINAPI 모두 확장). 표준 C 호출 규칙을 사용하는 함수 포인터를 통과하는 경우 (cdecl), 악이 발생합니다.
  • C ++에서 클래스 멤버 함수 포인터와 일반 기능 포인터 사이를 캐스트하십시오. 이것은 종종 C ++ 초보자를 여행합니다. 클래스 멤버 기능은 숨겨져 있습니다 this 매개 변수, 그리고 멤버 함수를 일반 함수로 캐스팅하면 this 사용해야 할 대상이되고 다시 많은 악이 발생합니다.

때때로 효과가 있지만 정의되지 않은 행동이라는 또 다른 나쁜 생각 :

  • 기능 포인터와 일반 포인터 사이의 캐스팅 (예 : 캐스팅 void (*)(void) a void*). 기능 포인터는 정기적 인 포인터와 반드시 크기가 같은 것은 아닙니다. 일부 아키텍처에서는 추가 상황 정보가 포함될 수 있기 때문입니다. 이것은 아마도 x86에서 잘 작동하지만 정의되지 않은 동작임을 기억하십시오.

다른 팁

나는 이것에 대해 물었을 정확히 동일한 문제에 대해 일부에서 코드를 입심였습니다.(옵션을 가지는 것 보다는 핵심 라이브러리에 대한 그놈 프로젝트 및 C.)라는 말을 전체 슬롯'n'signals framework 에 따라 달라집니다.

코드를 통해,수많은 인스턴스의 주조에서류(1)(2):

  1. typedef int (*CompareFunc) (const void *a, const void *b)
  2. 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

답을 위한 가능성이 올바른-- 는 경우 당신이 앉아 표준에 대한 위원이었습니다.아담과 요하네스 크레딧을 받을 자격이 그들의 잘 응답합니다.그러나,야생에서,당신은 이 코드의 잘 작동합니다.논란?그렇습니다.이것을 고려하십시오:입심이 컴파일되/works/테스트에서 많은 수의 플랫폼(Linux/Solaris/Windows/mac OS X)의 다양한 컴파일러/링커/kernel 로더(GCC/그램/MSVC).기준,저주 추측합니다.

나는 약간의 시간을 보냈다는 생각에 대한 답변이 있습니다.여기에 내 결론:

  1. 서면 당신은 라이브러리 콜백이 될 수 있습니다 확인.주의의 위험 부담--사용하여 자신의 위험이 있습니다.
  2. 다른 사람,그것을 하지 않는다.

생각이 깊은 후 이것을 쓰고 응답,나는 놀라지 않을 경우에는 코드는 C 컴파일러를 사용하여 이와 동일한다.그리고 이후(가장/all?) 현 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!) :

두 개의 다른 번역 단위의 구조, 노조 또는 열거 유형 선언은 번역 단위가 자체적으로 분리되기 때문에 이러한 선언의 텍스트가 동일한 파일에서 나온 경우에도 공식적으로 동일한 유형을 선언하지 않습니다. 따라서 표준은 그러한 유형에 대한 추가 호환성 규칙을 지정하므로 두 개의 선언이 충분히 유사하면 호환됩니다.

즉, DO_STUFF 기능이나 다른 사람이 기능 포인터가있는 기능을 호출하기 때문에 이것은 정의되지 않은 동작입니다. void* 매개 변수로하지만 기능에는 양립 할 수없는 매개 변수가 있습니다. 그럼에도 불구하고, 나는 모든 컴파일러가 신음하지 않고 컴파일하고 실행할 것으로 기대합니다. 그러나 다른 기능을 수행하여 클리너를 할 수 있습니다. void* (그리고 콜백 함수로 등록) 실제 함수를 호출합니다.

C 코드는 포인터 유형에 대해 전혀 신경 쓰지 않는 명령어로 컴파일하므로 언급 한 코드를 사용하는 것은 괜찮습니다. 콜백 함수로 do_stuff를 실행하고 다른 무언가에 대한 포인터보다 my_struct 구조를 인수로 실행할 때 문제가 발생합니다.

효과가없는 것을 보여줌으로써 더 명확하게 만들 수 있기를 바랍니다.

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 ++에서 작동하는 방식에 대해 생각하면 스택의 특정 항목을 푸시하고 새 코드 위치로 점프하고 실행 한 다음 스택을 반환합니다. 기능 포인터가 동일한 리턴 유형과 동일한 숫자/크기의 인수를 가진 함수를 설명하면 괜찮을 것입니다.

따라서 나는 당신이 안전하게 그렇게 할 수 있어야한다고 생각합니다.

void 포인터는 다른 유형의 포인터와 호환됩니다. Malloc과 MEM 기능이 어떻게 작동하는지의 중추입니다.memcpy, memcmp) 일하다. 일반적으로 C에서 (C ++가 아닌) NULL 매크로로 정의되어 있습니다 ((void *)0).

C99의 6.3.2.3 (항목 1)을보십시오 :

공극에 대한 포인터는 불완전한 또는 객체 유형으로 포인터에서 또는 포인터에서 변환 될 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top