programing

C function 포인터를 void 포인터로 캐스팅

closeapi 2023. 9. 18. 21:26
반응형

C function 포인터를 void 포인터로 캐스팅

다음 프로그램을 실행하려고 하는데 몇 가지 이상한 오류가 발생합니다.

파일 1.c:

typedef unsigned long (*FN_GET_VAL)(void);

FN_GET_VAL gfnPtr;

void setCallback(const void *fnPointer)
{
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

파일 2.c:

extern FN_GET_VAL gfnPtr;

unsigned long myfunc(void)
{
    return 0;
}

main()
{
   setCallback((void*)myfunc);
   gfnPtr(); /* Crashing as value was not properly 
                assigned in setCallback function */
}

여기 gfnPtr()은 gcc로 컴파일할 때 64비트 사용 리눅스에서 충돌합니다.하지만 gfnPtr() VC6와 SunOS를 성공적으로 호출했습니다.

하지만 아래와 같이 기능을 변경하면 성공적으로 작동하고 있습니다.

void setCallback(const void *fnPointer)
{
    int i; // put any statement here
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

문제의 원인을 좀 도와주시기 바랍니다.감사해요.

C 표준에서는 함수 포인터를 다음에 캐스트할 수 없습니다.void*. 다른 기능 포인터 유형에만 캐스트할 수 있습니다.C11 표준에서 6.3.2.3 §8:

한 유형의 함수에 대한 포인터를 다른 유형의 함수에 대한 포인터로 변환했다가 다시 되돌릴 수 있습니다.

중요한 것은 포인터를 사용하여 함수를 호출하기 전에 원래 유형으로 되돌려야 합니다(기술적으로는 호환되는 유형으로).6.2.7)에서 "호환성"의 정의.

모든 C 컴파일러가 사용되는 상황 때문에 POSIX 표준도 따라야 하며, 함수 포인터는 다음으로 함수 포인터를 다음으로 변환할 수 많은 (모든 컴파일러는 아닙니다.)void*그리고 뒤로.이는 일부 시스템 기능(예: 시스템 기능)에 필요합니다.dlsym).

POSIX 등에서는 그러한 캐스트가 필요하지만, 안타깝게도 표준에서는 데이터 포인터와 함수 포인터 간의 캐스트를 허용하지 않습니다.한 가지 해결책은 포인터를 캐스팅하는 것이 아니라 포인터를 포인터에 캐스팅하는 것입니다(이것은 컴파일러에 의해 괜찮으며 모든 정상적인 플랫폼에서 작업이 완료됩니다).

typedef void (*FPtr)(void); // Hide the ugliness
FPtr f = someFunc;          // Function pointer to convert
void* ptr = *(void**)(&f);  // Data pointer
FPtr f2 = *(FPtr*)(&ptr);   // Function pointer restored

데이터 포인터와 코드 포인터에 관해서는 세 가지 경험칙이 있습니다.

  • 데이터 포인터와 코드 포인터를 섞지 않음
  • 데이터 포인터와 코드 포인터를 섞지 않음
  • 데이터 포인터와 코드 포인터를 절대로 섞지 마십시오!

다음 기능에서:

void setCallback(const void *fnPointer)
{
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

함수 포인터의 경우 데이터 포인터가 있습니다.(이것은 포인터 자체의 주소를 먼저 가져가서 포인터에 던져 놓고 참조를 해제하는 것은 말할 것도 없습니다.)

다음과 같이 다시 써 봅니다.

void setCallback(FN_GET_VAL fnPointer)
{
     gfnPtr = fnPointer;
}

또한 포인터를 설정할 때 캐스트를 떨어뜨릴 수도 있습니다.

main()
{
   setCallback(myfunc);
   gfnPtr();
}

추가적인 보너스로 컴파일러가 수행하는 일반 유형 검사를 사용할 수 있습니다.

가능한 부분적인 설명을 제안하겠습니다.

@Manoj 두 컴파일러에서 생성된 SetCallback에 대한 어셈블리 목록을 검토(또는 제공)하면 최종 답변을 얻을 수 있습니다.

먼저 파스칼 쿠크의 말이 맞으며 린디댄서는 콜백을 올바르게 설정하는 방법을 보여줍니다.제 대답은 실제 문제를 설명하려는 시도일 뿐입니다.

문제는 리눅스와 다른 플랫폼이 서로 다른 64비트 모델을 사용한다는 사실에서 비롯되었다고 생각합니다(위키피디아의 64비트 모델 참조).리눅스에서는 LP64(int는 32비트)를 사용합니다.다른 플랫폼에 대해서는 좀 더 자세한 정보가 필요합니다.SPARC64인 경우 ILP64(int는 64비트)를 사용합니다.

제가 알기로는 리눅스에서만 문제가 관찰되었고 int local 변수를 도입하면 사라졌습니다.최적화를 해제한 상태에서 시도해 보셨습니까?아마도 이 해킹은 최적화 작업을 수행해도 아무런 효과가 없을 것입니다.

두 64비트 모델 모두에서 포인터는 코드를 가리키든 데이터를 가리키든 상관없이 64비트여야 합니다.그러나 이러한 경우(예: 세그먼트 메모리 모델)가 아닐 가능성이 있습니다. 따라서 파스칼과 린디댄서의 조언입니다.

포인터의 크기가 같으면 스택 정렬 문제가 발생할 수 있습니다.로컬 int(Linux에서 32비트)를 도입하면 정렬이 변경될 수 있습니다.이는 void*와 함수 포인터의 정렬 요구 사항이 다를 경우에만 영향을 미칩니다.미심쩍은 시나리오.

그럼에도 불구하고 다양한 64비트 메모리 모델이 관측한 결과의 원인일 가능성이 높습니다.우리가 그것들을 분석할 수 있도록 조립품 목록을 제공하는 것을 환영합니다.

다른 사람들이 말하는 것과는 다르게, 네, 당신은 그것을 가질 수 있습니다.void*함수 포인터로서 포인터를 사용하지만 의미론은 그것과 함께 사용하기 매우 까다롭습니다.

, 은, , 로 캐스팅할 가 없습니다.void* 처럼 처럼 .당신의 코드를 이렇게 실행해서 수정해서 작업했습니다.

파일 1.c:

typedef unsigned long (*FN_GET_VAL)(void);

extern FN_GET_VAL gfnPtr;

void setCallback(const void *fnPointer)
{
    gfnPtr = ((FN_GET_VAL) fnPointer);
}

파일 2.c:

int main(void)
{
    setCallback(myfunc);
    (( unsigned long(*)(void) )gfnPtr)();
}

언급URL : https://stackoverflow.com/questions/5579835/c-function-pointer-casting-to-void-pointer

반응형