Post

Pointer

추적

  
주소
1284EA4371 (**a)
125F74581284EA43 (*a)
122EF560(&a)125F7458 (a)

②→
①↖

포인터는 1 2 순서로 이동하며 따라가면 편하다.

포인터는 주소, 0차, 1차, 2차, …로 생각하면 편하다.

    
주소0차1차2차
&aa*a**a
122EF560125F74581284EA4371

포인터를 지원한다는 것은 어떤 변수에 대해 &연산을 통해 변수의 주소를 구할 수 있음을 의미한다. 자바도 참조개념을 사용하지만 포인터를 지원하지 않는다고 하는 것은 이런 맥락에서다.

포인터 배열과 배열 포인터

1
2
3
int *p[4];    // 포인터 배열. [주소, 주소, 주소, 주소]
int (*p)[4];  // 배열 포인터. [크기 4인 배열의 주소를 담고있는 포인터]
배열 포인터의 경우 접근할 때도 (*p)[i]  접근해야 한다.

포인터 배열은 포인터들의 배열이다. (이 때 배열 그 자체가 포인터이기 때문에 헷갈릴 수 있다.) 배열 포인터는 배열을 가리키는 포인터다.

Parameter에 int **p로 적는 경우, 배열의 크기가 12라 했을 때 3*4인지 6*2인지 알 수 없기 때문에 column값을 명시해서 행 열을 구분할 수 있도록 적어야 한다.

1
2
int *p[4]
int p[][4]

row에 쓰면 안되고 무조건 column에 써야하는데, 효율성 때문인 것 같다.
row값을 명시하면 결과적으로 실제 값이 저장되어있는 메모리 공간에 접근했을 때 column이 몇인지 모르니 어디까지 읽어야할지 모르는건 똑같다.
그래서 row와 배열의 크기를 이용해 column을 얻기 위해 함수에서 배열의 크기를 다시 계산하는 과정이 필요하니까 그냥 column을 넘기도록 하는 듯.

pointer 변수의 주소

1
2
단일 pointer 경우     ( struct *p )                     p != p[0] 이고,
이중 pointer 경우도   ( struct **p | struct *p[] )      p != p[0] 이다.

두 경우 모두 *p == p[0]이며, p->m1 == p[0].m1 이다.

배열을 포함하여 그 어떤 경우도 type == type[0]인 경우는 없으며 type == &type[0], *type == type[0] 이다.

즉, pointer와 data는 분리되어 있다.

연산자 우선순위는 [] > *

*와 []의 차이 - &연산

data가 stack에 잡히는 배열( []이나 [][]... ) a의 경우 &aa나 출력이 같다. 배열 변수는 포인터 변수 처럼 사용하지만 포인터 변수와 완전히 동일하지는 않기 때문. 배열 변수의 경우 메모리에 이렇게 들어가 있다.

  
002DF7ECkbc

배열 변수는 포인터 변수 처럼 변수 자체와 데이터가 저장되는 곳이 분리되어 있지 않다. &aa를 출력하도록 했을 때 똑같이 출력 된다는 것은 내부적으로 컴파일러가 a가 배열인지 포인터인지 구분한다는 것을 의미하며 a를 출력하라는 명령이 들어오면 대신 &a를 출력하도록 한다는 것을 의미한다.

&연산 후 +1 해봐도 차이가 있다.

  • 포인터의 경우 &하면 p변수의 주소가 되고, +1하면 메모리의 주소 단위의 +1이므로 실제로는 +4가 된다.
  • 배열의 경우 &하면 배열 객체 자기자신을 의미하게된다.(sizeof 연산도 마찬가지다.) 따라서 +1하면 배열의 크기 단위의 +1이므로 실제로는 배열의 크기인 +16( 0x10 )이 된다.

*와 []의 차이 - stack seg, data seg

정상 :

1
2
char s[] = "Hello";
s[3] = '2';

Segmentation fault :

1
2
char *s = "Hello";
s[3] = '2';
  • s[]는 stack에 잡혀 언제든지 수정 가능하지만, *s는 “Hello” 문자열이 data seg에 잡혀 수정이 불가능하다.
  • *s는 가리키는 곳만 다른 문자열로 변경하면 되므로 s="world";같은 식으로 변경이 가능하지만, s[]로 선언했을 때, s="world" 하면 오류가 발생한다. s[]는 stack에 공간이 할당되어 있고 그 공간만 참조하므로 strcpy()를 이용해 해당 공간에 문자열을 덮어 써야 한다.
  • *smalloc()으로 heap에 공간을 잡는다면 s[3]='2';같은걸로 초기화/입력이 가능하지만 어차피 s = malloc() 한다는건 heap을 가리키면서 기존에 가리키고 있던 문자열은 버린다는 의미다.

함수도 포인터

1
2
3
4
5
6
7
8
/*    t.c    */
void* f(){
}
int main(){
   void* a = f;
   printf("%x\n", a);
   return 0;
}
1
2
3
4
5
$ ./t
40052d

gdb-peda$ print f
$1 = {<text variable, no debug info>} 0x40052d <f>

function pointer

1
2
3
4
5
6
7
8
9
10
11
12
int test(char ch){
   ...
}

// case 1
int (*fp)(char);        // declare
fp = test;

// case 2
typedef int (*type)(char);        // define
type fp;                          // declare
fp = test;
1
2
3
 return_value
typedef NTSTATUS (WINAPI *PFZWQUERYINFORMATION)(...)
 #define WINAPI __stdcall

항상 typedef 다음에 오는게 리턴값이다.

  • #pragma comment() 같은건 compiler에 의존적인 전처리 명령어다. 따라서 compiler로 visual C가 아니라 gcc를 사용하는 Dev C++같은 건 못알아 먹어서, project option의 parameter tap을 이용하는 등 옵션을 따로 사용해야한다. asm이나 SEH가 제대로 안되는 것도 gcc가 이를 지원하지 않기 때문.
  • hook_by_code("ntdll.dll", "ZwQuery", (PROC)NewZwQueryInformation); (PROC)NewZwQueryInformation같이 함수 자체를 파라미터로 넘기는 건, 함수의 주소를 넘기는 것으로 함수 포인터를 넘기는거랑 같다고 보면 된다. 어차피 함수 포인터도 함수를 가리키는 거니까. 함수도 변수처럼 어떤 공간에 “선언 및 할당”되어 있는 것이다.
This post is licensed under CC BY 4.0 by the author.