Programing

포인터 유효성 테스트(C/C++)

c10106 2022. 5. 24. 23:07
반응형

포인터 유효성 테스트(C/C++)

주어진 포인터가 "유효한"지 (물론 프로그램적으로) 판단할 수 있는 방법이 있는가?NULL 확인은 쉽지만 0x00001234 같은 것은?이러한 종류의 포인터를 무시하려고 할 때 예외/충돌이 발생한다.

크로스 플랫폼 방식이 선호되지만 플랫폼별(윈도우즈·리눅스용)도 괜찮다.

자세한 내용을 보려면 업데이트:문제는 케케묵은/자유로운/비정규화된 포인터가 아니라, 발신자로부터 포인터를 가져가는 API를 구현하고 있다(예: 문자열의 포인터, 파일 핸들 등).발신자는 (목적 또는 실수로) 잘못된 값을 포인터로 보낼 수 있다.충돌을 방지하려면 어떻게 해야 하는가?

리눅스 산하 C 프로그램이 실행되고 있는 메모리의 상태와 질문이 왜 어떤 맥락에서 적절한 정교한 해답을 가지고 있는지에 대해 자기성찰적으로 생각할 수 있는 세 가지 쉬운 방법이 여기에 있다.

  1. getpagesize()를 호출하고 포인터를 페이지 경계로 반올림한 후, mincore()를 호출하여 페이지가 유효한지, 혹시 프로세스 작업 세트의 일부인지 확인할 수 있다.이 작업에는 일부 커널 리소스가 필요하므로 이를 벤치마킹하여 이 함수를 호출하는 것이 API에서 적합한지 확인하십시오.만약 당신의 api가 인터럽트를 처리하거나 직렬 포트에서 메모리로 읽으려면, 예측 불가능한 동작을 피하기 위해 이것을 호출하는 것이 적절하다.
  2. stat()를 호출하여 사용 가능한 /proc/self 디렉토리가 있는지 확인한 후 /proc/self/maps를 열고 읽으면 포인터가 있는 영역에 대한 정보를 찾을 수 있다.proc에 대한 man 페이지, 프로세스 정보 사이비 파일 시스템을 연구한다.분명히 이것은 비교적 비용이 많이 들지만, 당신은 이진 검색을 사용하여 효율적으로 조회할 수 있는 배열로 구문 분석 결과를 캐싱하는 것을 피할 수 있을 것이다.또한 /proc/self/smaps도 고려하십시오.만약 당신의 api가 고성능 컴퓨팅을 위한 것이라면, 프로그램은 불균일 메모리 아키텍처인 numa에 대한 man 페이지 아래에 문서화된 /proc/self/numa에 대해 알고 싶을 것이다.
  3. get_mempolicy(MPOL_F_ADDR) 호출은 실행 스레드가 여러 개 있고 CPU 코어 및 소켓 리소스와 관련하여 균일하지 않은 메모리에 대한 선호도를 가지도록 작업을 관리하는 고성능 컴퓨팅 api 작업에 적합하다.그러한 api는 물론 포인터가 유효한지도 알려준다.

Microsoft Windows에는 Process Status API(NUMA API에도 있음)에 문서화된 QueryWorkingSetEx 기능이 있다.정교한 NUMA API 프로그래밍의 핵심으로서, 이 기능은 또한 간단한 "유효성을 위한 포인터 테스트 (C/C++)" 작업을 할 수 있게 해 줄 것이며, 따라서 최소 15년 동안 사용되지 않을 것 같다.

자세한 내용을 보려면 업데이트:문제는 오래된 포인터, 자유롭거나 초기화되지 않은 포인터가 아니라, 발신자로부터 포인터를 가져가는 API를 구현하고 있다.발신자는 (목적 또는 실수로) 잘못된 값을 포인터로 보낼 수 있다.충돌을 방지하려면 어떻게 해야 하는가?

너는 그 수표를 만들 수 없다.포인터가 "유효한"지 여부를 확인할 수 있는 방법은 없다.사람들이 포인터를 가져가는 기능을 사용할 때, 그 사람들은 그들이 무엇을 하고 있는지 알고 있다는 것을 믿어야 한다.0x4211을 포인터 값으로 전달하면 0x4211을 가리키는 점을 신뢰해야 한다.그리고 만약 그들이 "우발적으로" 어떤 물체에 부딪혔다면, 만약 당신이 어떤 무서운 운영 시스템 기능(IsValidPtr 또는 그 밖의 것)을 사용하더라도, 당신은 여전히 버그에 빠져 빠르게 실패하지 않을 것이다.

이러한 종류의 신호를 보내는 데 null 포인터를 사용하기 시작하고 라이브러리의 사용자에게 실수로 잘못된 포인터를 전달하는 경향이 있는 경우 포인터를 사용하면 안 된다고 말하십시오. 심각히 :)

Unix에서는 포인터 검사를 수행하고 다음과 같은 EFault를 반환하는 커널 syscall을 사용할 수 있어야 한다.

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

bool isPointerBad( void * p )
{
   int fh = open( p, 0, 0 );
   int e = errno;

   if ( -1 == fh && e == EFAULT )
   {
      printf( "bad pointer: %p\n", p );
      return true;
   }
   else if ( fh != -1 )
   {
      close( fh );
   }

   printf( "good pointer: %p\n", p );
   return false;
}

int main()
{
   int good = 4;
   isPointerBad( (void *)3 );
   isPointerBad( &good );
   isPointerBad( "/tmp/blah" );

   return 0;
}

반환:

bad pointer: 0x3
good pointer: 0x7fff375fd49c
good pointer: 0x400793

실제 파일 생성 코드패스로 이어질 가능성이 있고 그에 따른 근접 요구사항이 있을 수 있으므로 [아마도 액세스]보다 syscall을 사용하는 것이 더 나을 것이다.

피터 주스 대답은 꽤 괜찮다.이를 위한 "공식적인" 방법은 다음과 같다.

#include <sys/mman.h>
#include <stdbool.h>
#include <unistd.h>

bool is_pointer_valid(void *p) {
    /* get the page size */
    size_t page_size = sysconf(_SC_PAGESIZE);
    /* find the address of the page that contains p */
    void *base = (void *)((((size_t)p) / page_size) * page_size);
    /* call msync, if it returns non-zero, return false */
    int ret = msync(base, page_size, MS_ASYNC) != -1;
    return ret ? ret : errno != ENOMEM;
}

나 자신도 거의 같은 입장에 있기 때문에 당신의 질문에 많은 공감을 얻었다.나는 많은 응답자들이 말하고 있는 것과 그들이 옳다는 것에 감사한다 - 포인터를 공급하는 일상들은 유효한 포인터를 제공하는 이어야 한다.나의 경우, 그들이 포인터를 손상시킬 수 있었다는 것은 거의 상상할 수 없는 일이지만, 만약 그들이 관리했다면, 충돌하는 것은 MY 소프트웨어일 것이고, 비난을 받는 것은 ME일 것이다.-(-)

내 요구 사항은 분할 결함 후에도 계속하는 것이 아니라 - 위험할 것이다 - 종료하기 전에 고객에게 일어난 일을 보고하여 고객이 나를 탓하지 않고 코드를 수정할 수 있도록 하려는 것이다!

이렇게 해서 (Windows에서): http://www.cplusplus.com/reference/clibrary/csignal/signal/

개요를 설명하려면:

#include <signal.h>

using namespace std;

void terminate(int param)
/// Function executed if a segmentation fault is encountered during the cast to an instance.
{
  cerr << "\nThe function received a corrupted reference - please check the user-supplied  dll.\n";
  cerr << "Terminating program...\n";
  exit(1);
}

...
void MyFunction()
{
    void (*previous_sigsegv_function)(int);
    previous_sigsegv_function = signal(SIGSEGV, terminate);

    <-- insert risky stuff here -->

    signal(SIGSEGV, previous_sigsegv_function);
}

이제 이것은 내가 원하는 대로 행동하는 것처럼 보인다. - 하지만 누군가가 결함을 발견할 수 있다면, 나에게 알려줘!

실제로 특정 상황에서 어떤 작업이 수행될 수 있다. 예를 들어 문자열 포인터 문자열이 유효한지 확인하려면 쓰기(fd, buf, szie) syscall을 사용하면 마법에 도움이 될 수 있다. fd는 테스트를 위해 생성한 임시 파일의 파일 설명자가 되고 buf 포인터가 잘못된 쓰기()인 경우 tesing하는 문자열을 가리키는 파일 설명자가 될 수 있다.-1번과 에러노를 EFOT로 설정하여 buf가 접근 가능한 주소 공간 밖에 있음을 표시한다.

Win32/64에는 이를 위한 방법이 있다.포인터를 읽고 실패 시 던져지는 SEH 감지를 잡아 보십시오.던지지 않으면 유효한 포인터가 된다.

그러나 이 방법의 문제는 포인터에서 데이터를 읽을 수 있는지 없는지를 반환한다는 것이다.그것은 형식 안전이나 다른 불변제 수에 대한 보증을 하지 않는다.일반적으로 이 방법은 "그렇다, 나는 이제 지나간 시간에 기억 속에 있는 그 특정한 장소를 읽을 수 있다"는 말 외에는 거의 쓸모가 없다.

요컨대 이러지 말자;)

레이먼드 첸은 이 주제에 대한 블로그 포스트를 가지고 있다: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

AFAIK는 방법이 없다.메모리를 확보한 후에는 항상 포인터를 NULL로 설정하여 이러한 상황을 피하도록 해야 한다.

Windows(윈도우)에서는 다음과 같은 기능이 작동하는지 확인하십시오(일부에서는 이전에 제안된 바 있음).

 static void copy(void * target, const void* source, int size)
 {
     __try
     {
         CopyMemory(target, source, size);
     }
     __except(EXCEPTION_EXECUTE_HANDLER)
     {
         doSomething(--whatever--);
     }
 }

함수는 일부 클래스의 정적, 독립형 또는 정적 방법이어야 한다.읽기 전용으로 테스트하려면 로컬 버퍼에 데이터를 복사하십시오.내용을 수정하지 않고 쓰기 테스트를 하려면 다시 쓰십시오.첫 번째/마지막 주소만 테스트할 수 있다.포인터가 유효하지 않으면 컨트롤이 'doSomething'으로 전달된 다음 브래킷 외부에 전달된다.CString과 같이 파괴자가 필요한 것은 사용하지 마십시오.

Windows에서 다음 코드를 사용하십시오.

void * G_pPointer = NULL;
const char * G_szPointerName = NULL;
void CheckPointerIternal()
{
    char cTest = *((char *)G_pPointer);
}
bool CheckPointerIternalExt()
{
    bool bRet = false;

    __try
    {
        CheckPointerIternal();
        bRet = true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    return  bRet;
}
void CheckPointer(void * A_pPointer, const char * A_szPointerName)
{
    G_pPointer = A_pPointer;
    G_szPointerName = A_szPointerName;
    if (!CheckPointerIternalExt())
        throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!");
}

사용량:

unsigned long * pTest = (unsigned long *) 0x12345;
CheckPointer(pTest, "pTest"); //throws exception

이 기사 MEM10-C. 포인터 유효성 검사 기능을 정의하고 사용하십시오. 특히 Linux OS에서는 어느 정도 검사를 수행할 수 있다는 것을 알 수 있다.

링크에 설명된 방법은 말로크가 반환한 가장 높은 메모리 주소를 추적하고 누군가가 그 값보다 큰 포인터를 사용하려고 하는지 테스트하는 기능을 추가하는 것이다.그것은 아마도 한정된 우리일 것이다.

이 스레드에서 약간 위로 올라온 답변에 대해:

Windows용 IsBadReadPtr(), IsBadWritePtr(), IsBadCodePtr(), IsBadStringPtr().

내 충고는 그들에게서 떨어져 있으라는 것이다. 누군가가 이미 이 글을 올렸다. http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

같은 주제와 같은 저자에 의한 또 다른 게시물(내 생각에)은 http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx이다.

API 사용자가 잘못된 데이터를 보내면 중단하십시오.전달된 데이터가 나중까지 사용되지 않아 원인을 찾기가 더 어려워진 문제가 있는 경우 입력 시 문자열 등이 기록되는 디버그 모드를 추가하십시오.만약 그들이 나쁘다면 그것은 명백할 것이다.자주 발생하는 경우, API를 프로세스 밖으로 이동하여 기본 프로세스 대신 API 프로세스를 중단시키는 것이 가치가 있을 수 있다.

발신자가 유효하지 않은 포인터를 보내면서 발생하는 충돌을 막는 것은 찾기 어려운 소리 없는 버그를 만드는 좋은 방법이다.

당신의 API를 사용하는 프로그래머가 자신의 코드를 숨기는 것보다 망가뜨려서 가짜라는 명확한 메시지를 받는 것이 좋지 않을까?

첫째, 일부러 사고를 일으키려고 하는 발신자로부터 자신을 보호하려는 것은 의미가 없다고 본다.그들은 스스로 유효하지 않은 포인터로 접근하려고 노력함으로써 쉽게 이것을 할 수 있었다.다른 많은 방법들이 있다 - 그것들은 단지 당신의 기억이나 스택을 덮어쓸 수도 있다.만약 당신이 이런 종류의 것들로부터 보호해야 한다면, 당신은 통신을 위해 소켓이나 다른 IPC를 사용하여 별도의 프로세스로 실행되어야 한다.

우리는 파트너/고객/사용자가 기능을 확장할 수 있는 소프트웨어를 꽤 많이 쓴다.필연적으로 어떤 버그가든 먼저 우리에게 보고되기 때문에 플러그 인 코드에 문제가 있다는 것을 쉽게 보여줄 수 있는 것이 유용하다.또한 보안에 대한 우려가 있고 일부 사용자들은 다른 사용자들보다 더 신뢰할 수 있다.

우리는 성능/처리 요건 및 신뢰성에 따라 여러 가지 다른 방법을 사용한다.가장 선호하는 항목:

  • 소켓(데이터를 텍스트로 전달하는 것)을 사용하여 프로세스를 분리한다.

  • 공유 메모리를 사용하여 프로세스를 분리한다(대량의 데이터를 전달할 경우).

  • 메시지 대기열을 통해 동일한 프로세스 개별 스레드(단락 메시지가 자주 발생하는 경우)

  • 동일한 프로세스 별개의 스레드가 메모리 풀에서 할당된 모든 전달 데이터.

  • 직접 프로시저 호출을 통한 동일한 프로세스 - 모든 전달된 데이터가 메모리 풀에서 할당됨.

타사 소프트웨어를 다룰 때, 특히 소스 코드가 아닌 이진 파일로서 플러그인/라이브러리(binary)를 사용할 때, 우리는 결코 당신이 하고자 하는 것에 의존하지 않는다.

메모리 풀의 사용은 대부분의 상황에서 매우 쉬우며 비효율적일 필요가 없다.애초에 당신이 데이터를 할당했다면, 당신이 할당한 값에 대해 포인터를 확인하는 것은 사소한 일이다.또한 할당된 길이를 저장하고 데이터 전후에 "매직" 값을 추가하여 유효한 데이터 유형과 데이터 오버런을 확인할 수도 있다.

이를 위한 휴대용 방법은 없으며, 특정 플랫폼을 위해 이를 수행하는 것은 하드와 불가능 사이의 어느 곳이든 될 수 있다.어떤 경우든, 당신은 절대 그러한 체크에 의존하는 코드를 작성해서는 안 된다 - 애초에 포인터가 유효하지 않은 값을 차지하도록 내버려두지 말라.

임의의 포인터를 공개 API의 입력 매개 변수로 받아들이는 것은 그다지 좋은 정책이 아니다.정수, 끈, 구조체 같은 "일반적인 데이터" 타입을 갖는 것이 더 좋다. (물론 내부는 평범한 데이터가 있는 고전적인 구조체를 의미하며, 공식적으로는 모든 것이 구조체가 될 수 있다.

왜냐고? 다른 사람들이 말하듯이, 여러분에게 유효한 포인터가 주어졌는지 아니면 쓰레기를 가리키는 것인지 알 수 있는 표준적인 방법이 없기 때문이다.

하지만 때때로 당신은 선택의 여지가 없다 - 당신의 API는 포인터를 받아들여야 한다.

이런 경우 좋은 포인터를 건네는 것이 발신자의 의무다.NULL은 값으로 허용될 수 있지만 정크에 대한 포인터는 허용되지 않는다.

어떤 식으로든 다시 확인해 주시겠습니까?음, 그런 경우에 내가 한 일은 포인터가 가리키는 유형에 대한 불변성을 정의하고, 포인터를 받으면 (디버그 모드에서) 호출하는 것이었습니다.적어도 불변제가 고장나거나 고장나면 나쁜 값이 전달되었다는 것을 알고 있다.

// API that does not allow NULL
void PublicApiFunction1(Person* in_person)
{
  assert(in_person != NULL);
  assert(in_person->Invariant());

  // Actual code...
}

// API that allows NULL
void PublicApiFunction2(Person* in_person)
{
  assert(in_person == NULL || in_person->Invariant());

  // Actual code (must keep in mind that in_person may be NULL)
}

C++에는 일반적인 경우로서 포인터의 유효성을 시험하는 조항이 없다.NULL(0x00000000)이 나쁘다고 가정할 수 있으며, 다양한 컴파일러와 라이브러리는 디버깅을 쉽게 하기 위해 여기저기서 "특수 값"을 사용하는 것을 좋아할 수 있다(예를 들어, 비주얼 스튜디오에서 포인터가 0xCECECE로 나타나면 나는 내가 잘못했다는 것을 알고 있다) 그러나 사실은 포인터는 메모리에 대한 인덱스일 뿐이기 때문에 임포커에 가깝다.포인터가 "올바른" 지수인지 보기만 해도 알 수 있다.

dynamic_cast와 RTTI로 할 수 있는 여러 가지 요령들이 있는데, 가리키는 물체가 당신이 원하는 유형인지 확인하기 위해서 그것들 모두는 당신이 애당초 유효한 것을 가리키도록 요구한다.

프로그램이 "잘못된" 포인터를 탐지할 수 있는지 확인하고 싶다면 다음과 같이 하십시오.생성 즉시 NULL 또는 유효한 주소로 선언하는 모든 포인터를 설정하고 포인터가 가리키는 메모리를 확보한 후 즉시 NULL로 설정하십시오.만약 당신이 이 관행에 대해 부지런하다면, NULL을 확인하는 것이 당신에게 필요한 전부다.

사용하기 전과 후에 포인터를 NULL로 설정하는 것이 좋은 방법이다.클래스 내 포인터를 관리하는 경우(예: 문자열):

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void SetText( const char *text);
    char *GetText() const { return MyText; }
    void Clear();

private:
    char * MyText;
};


SomeClass::SomeClass()
{
    MyText = NULL;
}


SomeClass::~SomeClass()
{
    Clear();
}

void SomeClass::Clear()
{
    if (MyText)
        free( MyText);

    MyText = NULL;
}



void SomeClass::Settext( const char *text)
{
    Clear();

    MyText = malloc( strlen(text));

    if (MyText)
        strcpy( MyText, text);
}

다른 사람들이 말했듯이, 당신은 유효하지 않은 포인터를 신뢰성 있게 감지할 수 없다.잘못된 포인터가 사용할 수 있는 일부 형식을 고려하십시오.

넌 널 포인터도 가질 수 있어.그것은 당신이 쉽게 확인하고 무언가를 할 수 있는 것이다.

유효한 기억력이 없는 곳으로 가는 포인터가 있을 수 있다.유효한 메모리를 구성하는 것은 시스템의 런타임 환경이 주소 공간을 설정하는 방법에 따라 달라진다.유닉스 시스템에서는 대개 0에서 시작하여 몇 메가바이트로 이동하는 가상 주소 공간이다.임베디드 시스템에서는 상당히 작을 수 있다.어떤 경우에도 0시에 시작되지 않을 수 있다.앱이 감독자 모드 또는 이와 동등한 모드에서 실행 중인 경우 포인터가 실제 주소를 참조할 수 있으며, 실제 메모리로 백업되거나 백업되지 않을 수 있다.

데이터 세그먼트, bss, 스택 또는 힙 내부에서도 올바른 개체를 가리키지 않는 올바른 메모리 내부의 어딘가에 포인터가 있을 수 있다.이것의 변형은 물체에 나쁜 일이 일어나기 전에 유효한 물체를 가리키던 포인터다.이러한 맥락에서 나쁜 것은 할당 해제, 메모리 손상 또는 포인터 손상이다.

참조 대상의 정렬이 불법인 포인터와 같이 완전히 고정된 불법 포인터가 있을 수 있다.

세그먼트/오프셋 기반 아키텍처와 기타 홀수 포인터 구현을 고려할 때 문제는 더욱 심각해진다.이런 종류의 일은 보통 훌륭한 컴파일러와 유형의 현명한 사용에 의해 개발자로부터 숨겨지지만, 베일을 뚫고 운영체제와 컴파일러 개발자보다 더 똑똑한 것을 원한다면, 음, 할 수 있지만, 우연히 마주칠 수 있는 모든 문제를 처리할 수 있는 일반적인 방법은 단 한 가지도 없다.

네가 할 수 있는 최선의 방법은 충돌을 허용하고 좋은 진단 정보를 내놓는 거야.

일반적으로는 할 수 없다.여기 특히 고약한 사건이 하나 있다.

struct Point2d {
    int x;
    int y;
};

struct Point3d {
    int x;
    int y;
    int z;
};

void dump(Point3 *p)
{
    printf("[%d %d %d]\n", p->x, p->y, p->z);
}

Point2d points[2] = { {0, 1}, {2, 3} };
Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]);
dump(p3);

많은 플랫폼에서 이것은 다음과 같이 출력된다.

[0 1 2]

당신은 런타임 시스템이 메모리 비트를 잘못 해석하도록 강요하고 있지만, 이 경우에는 비트가 모두 이치에 맞기 때문에 충돌하지 않을 것이다.이것은 언어 설계의 일부분이다(C형 다형성을 가지고 있다.struct inaddr inaddr_in inaddr_in6)) 따라서 어떤 플랫폼에서도 그것으로부터 확실하게 보호할 수 없다.

위의 기사에서 얼마나 많은 오해를 불러일으킬 수 있는 정보를 읽을 수 있는지 믿을 수 없다...

그리고 심지어 마이크로소프트 msdn 문서에서도 IsBadPtr은 금지된다고 주장되고 있다.오, 글쎄 - 난 고장나는 것보다 일하는 게 더 좋아.기간 작업이 잘못 작동하더라도(최종 사용자가 애플리케이션을 계속 사용할 수 있는 한)

구글 검색을 통해 Windows에 대한 유용한 예를 찾을 수 없었다. 32비트 앱에 대한 솔루션을 찾았다.

http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver%2Fsrc%2FdrvCppLib%2Frtti.cpp&obid=58895&obtid=2&ovid=2

하지만 나는 64비트 앱도 지원해야 해서 이 솔루션이 나에게 효과가 없었어.

하지만 나는 와인의 소스 코드를 수집했고, 64비트 앱에서도 사용할 수 있는 비슷한 종류의 코드를 요리하는데 성공했다. 여기에 코드를 첨부했다.

#include <typeinfo.h>   

typedef void (*v_table_ptr)();   

typedef struct _cpp_object   
{   
    v_table_ptr*    vtable;   
} cpp_object;   



#ifndef _WIN64
typedef struct _rtti_object_locator
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    const type_info *type_descriptor;
    //const rtti_object_hierarchy *type_hierarchy;
} rtti_object_locator;
#else

typedef struct
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    unsigned int type_descriptor;
    unsigned int type_hierarchy;
    unsigned int object_locator;
} rtti_object_locator;  

#endif

/* Get type info from an object (internal) */  
static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr)  
{   
    cpp_object* cppobj = (cpp_object*) inptr;  
    const rtti_object_locator* obj_locator = 0;   

    if (!IsBadReadPtr(cppobj, sizeof(void*)) &&   
        !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) &&   
        !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator)))  
    {  
        obj_locator = (rtti_object_locator*) cppobj->vtable[-1];  
    }  

    return obj_locator;  
}  

그리고 다음 코드는 포인터가 유효한지 여부를 감지할 수 있으며, 일부 NULL 검사를 추가해야 할 수 있다.

    CTest* t = new CTest();
    //t = (CTest*) 0;
    //t = (CTest*) 0x12345678;

    const rtti_object_locator* ptr = RTTI_GetObjectLocator(t);  

#ifdef _WIN64
    char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator;
    const type_info *td = (const type_info*)(base + ptr->type_descriptor);
#else
    const type_info *td = ptr->type_descriptor;
#endif
    const char* n =td->name();

이것은 포인터로부터 클래스 이름을 얻는다 - 나는 그것이 너의 필요에 충분하다고 생각한다.

내가 여전히 두려워하는 한 가지는 위 코드 캡처에서 이미 3-4개의 API 호출이 이루어지고 있는 포인터 확인 성능이 시간에 중요한 애플리케이션에 대해 과잉 살상일 수 있다는 것이다.

예를 들어 C#/관리 c++ 통화와 비교하여 포인터 확인의 오버헤드를 측정할 수 있다면 좋을 것이다.

Windows용 IsBadReadPtr(), IsBadWritePtr(), IsBadCodePtr(), IsBadStringPtr().
이것들은 블록 길이에 비례하여 시간이 걸리기 때문에, 건전성 확인을 위해 나는 단지 시작 주소를 체크한다.

나는 여러 도서관이 어떤 방법을 써서 미참조 메모리 등을 확인하는 것을 본 적이 있다.나는 그들이 단순히 메모리 할당과 할당 해제 방법(malloc/free)을 '오버라이드'한다고 믿는다. 이것은 포인터를 추적하는 어떤 논리를 가지고 있다.네 사용 사례로는 과잉 살상이겠지만 한 가지 방법이 될 거야.

기술적으로 연산자 new(및 삭제)를 재정의하고 할당된 모든 메모리에 대한 정보를 수집할 수 있으므로 힙 메모리가 유효한지 확인하는 방법을 사용할 수 있다.그러나:

  1. 스택()에 포인터가 할당되었는지 확인할 방법이 여전히 필요함

  2. '유효한' 포인터를 정의해야 할 경우:

a) 해당 주소의 메모리가 할당됨

b) 해당 주소의 메모리는 개체의 시작 주소(예: 대규모 배열의 중간에 없는 주소)

c) 해당 주소의 메모리가 예상 유형의 개체의 시작 주소임

요점: 문제의 접근방식은 C++ 방법이 아니며, 함수가 유효한 포인터를 수신하도록 보장하는 몇 가지 규칙을 정의해야 한다.

그 수표는 C++로 할 방법이 없다.다른 코드가 잘못된 포인터를 통과하면 어떻게 해야 하는가?넌 추락해야 해. 왜?다음 링크를 확인하십시오. http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

추가된 답변의 부록:

포인터가 유효한 포인터를 나타내는 0, 1 및 -1의 세 값만 포함할 수 있다고 가정하고, 여기서 1은 유효하지 않은 포인터, -1은 유효하지 않은 포인터, 0은 유효하지 않은 포인터라고 가정하십시오.포인터가 NULL일 확률은 얼마인가? 모든 값이 동일하게 될 확률이 1/3이다. 자, 유효한 케이스를 꺼내서 모든 유효하지 않은 케이스에 대해 50:50의 비율로 모든 오류를 잡아낸다.좋아 보이시죠?4바이트 포인터에 맞게 크기를 조정하십시오.가능한 값은 2^32 또는 4294967294이다.이 중 단 하나의 값만 정확하고, 하나는 NULL이며, 당신은 여전히 4294967292의 다른 무효 사례를 남겨두고 있다.다시 계산: 유효하지 않은 환자(4294967292+ 1) 중 1명에 대한 검정이 있는 경우.대부분의 실제적인 목적을 위해 2.xe-10 또는 0의 확률.이것이 NULL 수표의 허무함이다.

최소한 리눅스에서는 이런 능력이 있는 새로운 드라이버는 아마 쓰기가 그리 어렵지 않을 겁니다.

반면에, 이렇게 프로그램을 만드는 것은 어리석은 짓일 것이다.네가 그런 일에 정말 구체적이고 일회적인 용도가 없다면, 나는 그것을 추천하지 않을 것이다.만약 당신이 일정한 포인터 유효성 검사를 로딩한 큰 어플리케이션을 만들었다면 그것은 끔찍할 정도로 느릴 것이다.

이러한 방법은 작동하지 않으므로 피해야 한다. blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx – JaredPar 2009년 2월 15일 16:02

만약 그것들이 작동하지 않는다면 - 다음 윈도우 업데이트는 그것을 고칠 것이다 ? 만약 그것들이 콘셉트 레벨에서 작동하지 않는다면 - 기능은 아마도 윈도우 api에서 완전히 제거될 것이다.

MSDN 문서에서는 이러한 포인터가 금지되어 있다고 주장하며, 그 이유는 아마도 추가 애플리케이션 설계의 결함(예: 일반적으로 전체 애플리케이션의 설계를 담당할 경우 무효 포인터를 조용히 먹어서는 안 됨)과 포인터 확인의 성능/시간 때문일 것이다.

하지만 당신은 그들이 어떤 블로그 때문에 작동하지 않는다고 주장해서는 안 된다.내 시험 지원서에서 나는 그들이 효과가 있다는 것을 확인했다.

이 링크는 도움이 될 수 있다.

_CrtIsValidPointer 지정된 메모리 범위가 읽기 및 쓰기에 유효한지 검증(디버그 버전만 해당)http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx

_CrtCheckMemory 디버그 힙에 할당된 메모리 블록의 무결성을 확인한다(디버그 버전만 해당).http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx

참조URL: https://stackoverflow.com/questions/551069/testing-pointers-for-validity-c-c

반응형