programing tip

일부 플랫폼에서는 char **, 다른 플랫폼에서는 const char **를받는 C ++ 함수를 이식 가능하게 호출 할 수있는 방법은 무엇입니까?

itbloger 2020. 9. 4. 07:01
반응형

일부 플랫폼에서는 char **, 다른 플랫폼에서는 const char **를받는 C ++ 함수를 이식 가능하게 호출 할 수있는 방법은 무엇입니까?


내 Linux (및 OS X) 컴퓨터에서 iconv()함수에는 다음 프로토 타입이 있습니다.

size_t iconv (iconv_t, char **inbuf...

FreeBSD에서는 다음과 같이 보입니다 :

size_t iconv (iconv_t, const char **inbuf...

내 C ++ 코드를 두 플랫폼 모두에서 빌드하고 싶습니다. C 컴파일러에서 매개 변수에 char**대해 전달 const char**(또는 그 반대)은 일반적으로 단순한 경고를 내 보냅니다. 그러나 C ++에서는 치명적인 오류입니다. 따라서를 전달하면 char**BSD에서 컴파일되지 않고, 전달하면 const char**Linux / OS X에서 컴파일되지 않습니다. 플랫폼을 감지하지 않고 두 가지 모두에서 컴파일되는 코드를 어떻게 작성할 수 있습니까?

한 가지 (실패한) 아이디어는 헤더에서 제공하는 모든 것을 재정의하는 로컬 프로토 타입을 제공하는 것이 었습니다.

void myfunc(void) {
    size_t iconv (iconv_t, char **inbuf);
    iconv(foo, ptr);
}

이것은 iconvC 연결 필요 하기 때문에 실패 extern "C"하고 함수 내에 넣을 수 없습니다 (왜 안됩니까?)

내가 생각 해낸 최고의 작업 아이디어는 함수 포인터 자체를 캐스팅하는 것입니다.

typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);

그러나 이것은 더 심각한 다른 오류를 가릴 가능성이 있습니다.


원하는 것이 const 문제에 눈을 멀게하는 것이라면 구분을 흐리게하는 변환을 사용할 수 있습니다. 즉, char ** 및 const char **를 상호 운용 가능하게 만듭니다.

template<class T>
class sloppy {}; 

// convert between T** and const T** 
template<class T>
class sloppy<T**>
{
    T** t;
    public: 
    sloppy(T** mt) : t(mt) {}
    sloppy(const T** mt) : t(const_cast<T**>(mt)) {}

    operator T** () const { return t; }
    operator const T** () const { return const_cast<const T**>(t); }
};

그런 다음 나중에 프로그램에서 :

iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);

sloppy ()는 a char**또는 a const char*를 받아서 iconv의 두 번째 매개 변수가 요구하는대로 a char**또는 a 로 변환합니다 const char*.

업데이트 : const_cast를 사용하고 as cast가 아닌 sloppy를 호출하도록 변경되었습니다.


선언 된 함수의 시그니처를 검사하여 두 선언을 명확하게 할 수 있습니다. 다음은 매개 변수 유형을 검사하는 데 필요한 템플릿의 기본 예입니다. 이것은 쉽게 일반화 될 수 있지만 (또는 Boost의 기능 특성을 사용할 수 있음) 특정 문제에 대한 솔루션을 입증하기에 충분합니다.

#include <iostream>
#include <stddef.h>
#include <type_traits>

// I've declared this just so the example is portable:
struct iconv_t { };

// use_const<decltype(&iconv)>::value will be 'true' if the function is
// declared as taking a char const**, otherwise ::value will be false.
template <typename>
struct use_const;

template <>
struct use_const<size_t(*)(iconv_t, char**, size_t*, char**, size_t*)>
{
    enum { value = false };
};

template <>
struct use_const<size_t(*)(iconv_t, char const**, size_t*, char**, size_t*)>
{
    enum { value = true };
};

다음은 동작을 보여주는 예입니다.

size_t iconv(iconv_t, char**, size_t*, char**, size_t*);
size_t iconv_const(iconv_t, char const**, size_t*, char**, size_t*);

int main()
{
    using std::cout;
    using std::endl;

    cout << "iconv: "       << use_const<decltype(&iconv)      >::value << endl;
    cout << "iconv_const: " << use_const<decltype(&iconv_const)>::value << endl;
}

매개 변수 유형의 규정을 감지 할 수 있으면 호출하는 두 개의 래퍼 함수를 ​​작성할 수 있습니다 iconv. 하나 iconvchar const**인수로 호출하고 다른 하나 iconvchar**인수로 호출합니다 .

함수 템플릿 전문화는 피해야하므로 클래스 템플릿을 사용하여 전문화합니다. 또한 우리가 사용하는 전문화 만 인스턴스화되도록 각 호출자를 함수 템플릿으로 만듭니다. 컴파일러가 잘못된 전문화에 대한 코드를 생성하려고하면 오류가 발생합니다.

그런 다음 call_iconv이를 iconv직접 호출하는 것처럼 간단하게 호출하기 위해 사용을 래핑합니다 . 다음은 이것이 작성되는 방법을 보여주는 일반적인 패턴입니다.

template <bool UseConst>
struct iconv_invoker
{
    template <typename T>
    static size_t invoke(T const&, /* arguments */) { /* etc. */ }
};

template <>
struct iconv_invoker<true>
{
    template <typename T>
    static size_t invoke(T const&, /* arguments */) { /* etc. */ }
};

size_t call_iconv(/* arguments */)
{
    return iconv_invoker<
        use_const<decltype(&iconv)>::value
    >::invoke(&iconv, /* arguments */);
}

(이 후자의 논리는 정리되고 일반화 될 수 있습니다. 저는 그것이 작동하는 방식을 더 명확하게하기 위해 각 부분을 명시 적으로 만들려고 노력했습니다.)


다음을 사용할 수 있습니다.

template <typename T>
size_t iconv (iconv_t i, const T inbuf)
{
   return iconv(i, const_cast<T>(inbuf));
}

void myfunc(void) {
  const char** ptr = // ...
  iconv(foo, ptr);
}

통과 할 수 있으며 const char**Linux / OSX에서는 템플릿 기능을 통과하고 FreeBSD에서는 iconv.

단점 : iconv(foo, 2.5)컴파일러를 무한 반복 상태로 만드는 것과 같은 호출을 허용합니다 .


#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

여기 에 모든 운영 체제의 ID가 있습니다. 저에게는이 시스템을 확인하지 않고 운영 체제에 의존하는 작업을 시도 할 필요가 없습니다. 초록색 바지를 사는 것과 같지만 보지 않고.


자신의 래퍼 기능을 사용할 수 있음을 표시했습니다. 당신은 또한 기꺼이 경고와 함께 살고있는 것 같습니다.

따라서 래퍼를 C ++로 작성하는 대신 C로 작성하면 일부 시스템에서만 경고가 표시됩니다.

// my_iconv.h

#if __cpluscplus
extern "C" {
#endif

size_t my_iconv( iconv_t cd, char **restrict inbuf, ?* etc... */);


#if __cpluscplus
}
#endif



// my_iconv.c
#include <iconv.h>
#include "my_iconv.h"

size_t my_iconv( iconv_t cd, char **inbuf, ?* etc... */)
{
    return iconv( cd, 
                inbuf /* will generate a warning on FreeBSD */,
                /* etc... */
                );
}

어때

static void Test(char **)
{
}

int main(void)
{
    const char *t="foo";
    Test(const_cast<char**>(&t));
    return 0;
}

편집 : 물론 "플랫폼을 감지하지 않고"는 약간의 문제입니다. 죄송합니다 :-(

편집 2 : 좋아, 개선 된 버전일까요?

static void Test(char **)
{
}

struct Foo
{
    const char **t;

    operator char**() { return const_cast<char**>(t); }
    operator const char**() { return t; }

    Foo(const char* s) : t(&s) { }
};

int main(void)
{
    Test(Foo("foo"));
    return 0;
}

이건 어떤가요:

#include <cstddef>
using std::size_t;

// test harness, these definitions aren't part of the solution
#ifdef CONST_ICONV
    // other parameters removed for tediousness
    size_t iconv(const char **inbuf) { return 0; }
#else
    // other parameters removed for tediousness
    size_t iconv(char **inbuf) { return 0; }
#endif

// solution
template <typename T>
size_t myconv_helper(size_t (*system_iconv)(T **), char **inbuf) {
    return system_iconv((T**)inbuf); // sledgehammer cast
}

size_t myconv(char **inbuf) {
    return myconv_helper(iconv, inbuf);
}

// usage
int main() {
    char *foo = 0;
    myconv(&foo);
}

I think this violates strict aliasing in C++03, but not in C++11 because in C++11 const char** and char** are so-called "similar types". You aren't going to avoid that violation of strict aliasing other than by creating a const char*, set it equal to *foo, call iconv with a pointer to the temporary, then copy the result back to *foo after a const_cast:

template <typename T>
size_t myconv_helper(size_t (*system_iconv)(T **), char **inbuf) {
    T *tmpbuf;
    tmpbuf = *inbuf;
    size_t result = system_iconv(&tmpbuf);
    *inbuf = const_cast<char*>(tmpbuf);
    return result;
}

This is safe from the POV of const-correctness, because all iconv does with inbuf is increment the pointer stored in it. So we're "casting away const" from a pointer derived from a pointer that was non-const when we first saw it.

We could also write an overload of myconv and myconv_helper that take const char **inbuf and messes things about in the other direction, so that the caller has the choice whether to pass in a const char** or a char**. Which arguably iconv should have given to the caller in the first place in C++, but of course the interface is just copied from C where there's no function overloading.


Update: now I see that it is possible to handle it in C++ without autotools, yet I'm leaving the autoconf solution for people looking for it.

What you're looking for is iconv.m4 which is installed by gettext package.

AFAICS it's just:

AM_ICONV

in configure.ac, and it should detect the correct prototype.

Then, in the code you use:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif

I am late to this party but still, here is my solution:

// This is here because some compilers (Sun CC) think that there is a
// difference if the typedefs are not in an extern "C" block.
extern "C"
{
//! SUSv3 iconv() type.
typedef size_t (& iconv_func_type_1) (iconv_t cd, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft); 


//! GNU iconv() type.
typedef size_t (& iconv_func_type_2) (iconv_t cd, const char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft);
} // extern "C"

//...

size_t
call_iconv (iconv_func_type_1 iconv_func, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft)
{
    return iconv_func (handle, inbuf, inbytesleft, outbuf, outbytesleft);
}

size_t
call_iconv (iconv_func_type_2 iconv_func, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft)
{
    return iconv_func (handle, const_cast<const char * *>(inbuf),
        inbytesleft, outbuf, outbytesleft);
}

size_t
do_iconv (char * * inbuf, size_t * inbytesleft, char * * outbuf,
    size_t * outbytesleft)
{
    return call_iconv (iconv, inbuf, inbytesleft, outbuf, outbytesleft);
}

참고URL : https://stackoverflow.com/questions/11421439/how-can-i-portably-call-a-c-function-that-takes-a-char-on-some-platforms-and

반응형