Pointer
추적
주소 | 값 |
1284EA43 | 71 (**a ) |
125F7458 | 1284EA43 (*a ) |
122EF560(&a ) | 125F7458 (a ) |
②→
①↖
포인터는 1 2 순서로 이동하며 따라가면 편하다.
포인터는 주소, 0차, 1차, 2차, …로 생각하면 편하다.
주소 | 0차 | 1차 | 2차 |
&a | a | *a | **a |
122EF560 | 125F7458 | 1284EA43 | 71 |
포인터를 지원한다는 것은 어떤 변수에 대해 &
연산을 통해 변수의 주소를 구할 수 있음을 의미한다. 자바도 참조개념을 사용하지만 포인터를 지원하지 않는다고 하는 것은 이런 맥락에서다.
포인터 배열과 배열 포인터
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
의 경우 &a
나 a
나 출력이 같다. 배열 변수는 포인터 변수 처럼 사용하지만 포인터 변수와 완전히 동일하지는 않기 때문. 배열 변수의 경우 메모리에 이렇게 들어가 있다.
002DF7EC | kbc |
배열 변수는 포인터 변수 처럼 변수 자체와 데이터가 저장되는 곳이 분리되어 있지 않다. &a
와 a
를 출력하도록 했을 때 똑같이 출력 된다는 것은 내부적으로 컴파일러가 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()
를 이용해 해당 공간에 문자열을 덮어 써야 한다.*s
도malloc()
으로 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
같이 함수 자체를 파라미터로 넘기는 건, 함수의 주소를 넘기는 것으로 함수 포인터를 넘기는거랑 같다고 보면 된다. 어차피 함수 포인터도 함수를 가리키는 거니까. 함수도 변수처럼 어떤 공간에 “선언 및 할당”되어 있는 것이다.