포인터 인수를 사용하여 C ++ 메서드를 C 함수로 변환 할 수 있습니까?


16

ESP-32에서 C ++을 사용합니다. 타이머를 등록 할 때 다음을 수행해야합니다.

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

여기 타이머가 호출합니다 soundCallback.

작업을 등록 할 때도 마찬가지입니다.

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

따라서이 방법은 분리 된 작업에서 시작됩니다.

GCC는 항상 이러한 전환에 대해 경고하지만 계획대로 작동합니다.

프로덕션 코드에서 허용됩니까? 더 좋은 방법이 있습니까?

답변:


47

당신이하고있는 reinterpret_cast것을 정확히 알지 않는 한 A 는 항상 비린내가 있습니다 . 여기서 코드는 C ++ 메소드에 대한 GCC의 호출 규칙으로 만 작동하지만 정의되지 않은 동작과 같은 냄새가납니다. 특히 멤버 함수가 어떤 식 으로든 일반 함수 포인터와 호환된다고 가정해서는 안됩니다.

일반적인 접근 방식은 대신 내부적으로 C ++ 메소드를 호출하는 적절한 서명으로 C 호환 함수를 정의하는 것입니다. 예를 들면 다음과 같습니다.

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

이 캐스트는 a void*에서 뾰족한 객체의 유형으로 다시 캐스팅하기 때문에 좋습니다.

세부:

  • extern "C"이 함수 의 언어 연결 을 지정합니다 . 언어 연결은 이름 맹 글링과 함수 의 호출 규칙 에 영향을줍니다 . 멤버 함수는 C 언어 링크를 가질 수 없습니다. 언어 연결은 대부분 내부 / 외부 연결과 직교합니다.

  • 콜백의 경우 함수는 "비공개"일 수 있습니다. 즉 내부 연결이 있습니다. C 코드는 이름으로 콜백을 참조하지 않습니다. 위의 코드 스 니펫은 static키워드를 통한 내부 연결을 지정 합니다 (정적 방법 아님). 또는 함수가 익명 네임 스페이스에 배치되었을 수 있습니다.

    나는 사이의 상호 작용에 대해 완전히 확실하지 오전 extern "C"static(내부 연결). 예를 들어 [dcl.link],“모든 함수 유형, 외부 연결이있는 함수 이름 및 외부 연결이있는 변수 이름에는 언어 연결이 있습니다.”라고 C 언어 연결 유형my_timer_callback있지만 함수 이름 이 그렇지 않도록 해석합니다 .

  • A static_cast는 실제 유형을 알고 arg있지만 유형 시스템 내에서 표현할 수 없기 때문에 여기에 적합 합니다. 대조적으로, a reinterpret_cast는 비트 패턴, 예를 들어 숫자 형에 대한 포인터를 재 해석하고자 할 때 적합합니다.

  • 함수는 일반적인 객체가 아니며 멤버 함수는 훨씬 적습니다. 함수가 실제 유형을 통해서만 호출되는 한 (및 멤버 함수 포인터와 유사하게) 함수 포인터 유형간에 재 해석 될 수 있습니다. 함수 포인터를 다른 유형 (예 : 객체 포인터 또는 void 포인터)으로 캐스트 할 수 있는지 여부는 구현 정의 ( background )입니다. POSIX는 함수 포인터 사이의 캐스트와에 void*있도록 허용 dlsym()캔 작동합니다. (멤버) 함수 포인터와 관련된 다른 캐스트는 정의되어 있지 않습니다. 특히 멤버 함수와 함수 포인터 사이의 캐스트는 불가능합니다.


1
std::bind객체 포인터를 첫 번째 메소드 인수로 가정 하지 않습니까?
발은

5
@val 예, 그러나 멤버 함수가 일반 함수와 호환된다는 것을 의미하지는 않으며, bind ()는 멤버 함수를 일반 함수 객체와 별도의 경우로 처리 하는 INVOKE 알고리즘 을 사용합니다 . 함수 포인터. std :: bind ()가 functor를 생성하기 때문에 C와의 인터페이스에 적합하지 않습니다.
amon

1
또 다른 질문 : 왜 내가 extern "C"여기에 필요 합니까? 이 경우 C 연계가 중요합니까?
val은

5
@val C에서 해당 함수를 호출하려면 C 호출 규칙을 사용해야합니다. 이 기능은 C 언어 연결 또는 컴파일러 특정 확장 ()과 함께 해당 함수를 선언하여 수행 할 수 있습니다 (예 __attribute__((cdecl)): 수행하지 마십시오). C ++ 함수는 그렇지 않으면 C 호환 호출 규칙을 보장하지 않습니다 (GCC에서는 일반적으로 잘 작동합니다).
amon

4
@val extern "C"공식적으로 필요한 이유에 대한 자세한 내용 은 [dcl.link]"언어 연결이 다른 두 가지 함수 유형이 다른 경우에도 서로 다른 유형입니다."를 참조하십시오. 및 [expr.call]"함수 유형이 호출 된 함수 정의의 함수 유형과 다른 표현식을 통해 함수를 호출하면 정의되지 않은 동작이 발생 함"
Ben Voigt

-1

개인적으로, 내가 찾은 가장 호환 가능하고 구현하기 쉽고 이해하기 쉬운 접근 방식은 내부적으로 메소드를 호출하는 예상 C 인터페이스와 호환되는 "래퍼"기능을 제공하는 것입니다 (정적이지 않은 경우, 기존 인스턴스를 인스턴스화하거나 사용하여 수행하십시오). 어댑터 디자인 패턴의 변형으로 볼 수 있습니다.


6
그게 아몬이 대답하지 않습니까?
Dronz

1
두 번째 읽은 후에 @Dronz, 그렇습니다. 대부분입니다. 내가 읽 자마자 나는 static그것을 방법으로 보았고 어떤 이유로 this포인터를 첫 번째 인수로 전달하지 않는다는 것을 깨닫지 못했습니다 ( std::bind강화 사용에 대한 다음 토론 ). 그러나 그렇습니다, 당신은 절대적으로 맞습니다! (이중 답변 죄송합니다!)
예수 알론소 아 바드

3
예, static적어도 세 가지의 다른 의미가 있습니다. 그리고 조심하지 않으면 그것들을 섞을 것입니다. 나는 static각각의 자체가 훌륭한 도구이기 때문에의 서로 다른 용도 사이의 차이점을 이해하는 것이 정말 도움이된다고 말하고 싶습니다 .
cmaster-monica reinstate 모니카
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.