*&*&*&*&*&指標
Arvin Liu @ 2020 資訊之芽 語法班
指標對初心者來說不好理解
有問題要盡量發問唷!
( ≧Д≦)
變數是怎麼儲存的?
程式中,很像有這樣一個表...
第0個櫃子
第1個櫃子
第2個櫃子
「放學校的書的櫃子」
「放輕小說的櫃子」
「放『男人都會有一些不可告人的本子』的櫃子」
放了國文課本。
放了「涼宮春日的憂鬱」。
放了
??????。
用精簡的表示方法:
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
第0個櫃子 (0x00) | 放學校的書的櫃子 | 國文課本。 |
第1個櫃子 (0x01) | 放輕小說的櫃子 | 涼宮春日的憂鬱。 |
第2個櫃子 (0x02) | 放『男人都會有一些不可告人的本子』的櫃子 | ????? |
地址。我們通常用十六進位表示。
變數名稱。
就....它放/存了什麼。
所以程式到底?
int a = 5;
這短短的一行到底做了什麼?
流程 of
int a = 5;
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 |
int x | 7 |
0x00001 | 空的。 | 沒放東西。 |
0x00002 | 空的。 | 沒放東西。 |
1. 找一個沒人用的櫃子。
2. 讓別人知道a在哪裡。
3. 把值放進去。
int a
5
取址& 取值*
& 取址符號 (拿地址)
* 取值符號 (拿數值)
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | float a | 1.1 |
0x00008 | double b | 1.0 |
假設程式的記憶體表格:
那麼 &x 就是 0x00000。
&
*
但這裡有個疑問,&x是什麼型態?
我們給一個特別的型態表示 int的地址: int*
所以 & 可以把int 變成 int* , * 可以把int*變回int
所以 & 可以把float 變成 float* , * 可以把float*變回float
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | float a | 1.1 |
0x00008 | double b | 1.0 |
假設程式的記憶體表格:
那麼 &x 就是 0x00000。
*(0x00000) 呢? 會編譯失敗。 因為0x00000是int,不是int*
*(int *)(0x00000) 呢? 7
*(float *)(0x00004) 呢? 1.1
&*(float *)(0x00004) 呢? 0x00004
int* 的 * 跟取值符號的 *不要搞混喔!!
一個是型態的一部分 (拿來當宣告用的),
一個是取值符號(拿來使用,當運算的)。
Example!
#include <iostream>
int main(){
int x=777;
std::cout << &x << std::endl;
//std::cout << *(int *)(0x7fffffffdc54) << std::endl;
std::cout << *&x << std::endl;
std::cout << &*&x << std::endl;
std::cout << *&*&x << std::endl;
//std::cout << *(0x7fffffffdc54) << std::endl; // CE
//std::cout << *x << std::endl; // CE
//std::cout << **&*&*&x << std::endl; // CE
//std::cout << *(int *)x << std::endl; // RE
}
/* Output -- Answers are here.
0x7fffffffdc54
777
777
0x7fffffffdc54
777
*/
Practice Time!
除了練習上面的東西,觀察你變數的地址以外,
試試看 *(int *) 一個隨便給的數字會怎麼樣?
小小提醒:
你們是猜不出0x61ff0c這種東西的,因為每次都不一樣。
所以你們直接註解那行掉就可以囉!
* 小知識:現今電腦都有ASLR(位址空間組態隨機載入)保護機制,所以每一次執行的&x都不一樣。除非你把它關掉,你才可以預先知道&x是什麼。這邊看不懂沒關係,想了解可以自行google看看。
變數地址的變數的宣告
也就是 - 指標宣告
What is Pointer?
指標就是一個變數。
它存著別人的地址。
如果你要存int的地址,
那麼它型態就是 int * 。
Pointer Init
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00008 | (int *) a | 0x00000 |
0x00010 | (int **) b | 0x00008 |
int x = 7;
int *a = &x;
int **b = &a;
std::cout << *a << std::endl;
*a = 5;
std::cout << **b << std::endl;
5
**b = x , *b =a , *a = x
宣告指標的一些坑
int* a, b;
// 上下兩個等價
int *a;
int b;
// 所以宣告兩個指標要
int *a, *b;
小小總結
-
宣告時,在變數前加上一個*,即代表此變
數為一個指標 - 宣告時, *不要跟著型態,會搞混。(int *a,*b;)
-
使用時,在一個變數前加上一個&,其值即
為此變數的位址 -
使用時,在一個指標前加上一個*,其值即
為位在此位址之變數本身
題外話:sizeof
sizeof : 知道一個型態的大小是多少Bytes
1 Byte = 8 bits
1 bit 就是一個 0或1。
所以一個byte 就是 00000000 ~ 11111111
通常bit都會八個一組,我們稱之為byte。
各個型態多少byte(s)?
#include <iostream>
int is_admin = 0;
int ary[160];
int main(){
std::cout << sizeof(char) << std::endl;
std::cout << sizeof(int) << std::endl;
std::cout << sizeof(long long) << std::endl;
std::cout << sizeof(float) << std::endl;
std::cout << sizeof(int *) << std::endl;
std::cout << sizeof(double *) << std::endl;
}
// 1 4 8 4 8 8
Practice 0x01: 地址差多少?
#include <iostream>
int a;
int main(){
char c;
// 想辦法算出 &a - &c
}
hint : 我們知道指標的大小是 8 bytes,和longlong一樣。
answer : (long long) &a - (long long) &c
記憶體區段錯誤?
Segmentation Fault (會吃RE)
亂亂用 *
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | 系統的祕密。 | 不給看&寫>< |
0x00008 | 還沒規劃的地方。 | ??????? |
*(int *)0x00004 或 *(int *)0x00008 因為這兩塊還沒規劃或者電腦不給你看,所以當你在*的時候就會出現記憶體區段錯誤。
(例如程式已停止回應。)
亂亂用 *
所以只要你 * 的位置是系統開給你的,例如宣告變數/陣列,那麼通常就不會出現RE。
你懂什麼叫陣列嘛? part 1
你懂什麼是浪漫嗎?
陣列宣告
int a = 5;
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | 空的。 | 沒放東西。 |
0x00004 | 空的。 | 沒放東西。 |
0x00008 | int a | 5 |
之後程式自己的記憶體會長這樣子:
int a[4] = {0};
那這樣子呢?
陣列宣告
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0xFFF00 | a[0] | 0 |
0xFFF04 | a[1] | 0 |
0xFFF08 | a[2] | 0 |
0xFFF0A | a[3] | 0 |
... |
大概會像這樣子:
int a[4] = {0};
編譯器會讓 a 和 &a 都會等於 0xFFF00, 但 *a 是 a[0]。
陣列取值
int a[4] = {0};
a[1] = 1;
追根究底,a[1]是什麼意思?
其實就是 *(a + 1) 的意思。(a 是類似int *,詳情請見上一頁)
那,a + 1 是什麼意思呢?
就是a這個指標的地址 + 1個int的大小。
根據上面的原則。寫a[1]或寫1[a] 都沒有關係喔!
因為 1 + a = a + 1。
一個型態的大小可以用sizeof(type)來看。例如sizeof(int)就會是4,表示4Bytes。
Example2: magic of +
#include <iostream>
int main(){
int ary[10];
std::cout << ary << std::endl;
// 0x61fee8
std::cout << ary + 1 << std::endl;
// 0x61feec
std::cout << (long long)ary + 1 << std::endl;
// 6422249
std::cout << (int *)((long long)ary + 1) << std::endl;
// 0x61fee9
}
你懂什麼叫陣列嘛? part 2
為什麼陣列開太小會RE啊?
(執行時期錯誤)
例如我們宣告 int ary[160];
但是題目的N可能會到10000,
因此你就會存取到ary[1600]。
我們已經知道,
亂亂存取很容易吃RE。
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | 系統的祕密。 | 不給看&寫>< |
0x00008 | 還沒規劃的地方。 | ??????? |
也就是你戳到0x00004/0x00008的時候,電腦就會因為安全因素讓你RE。
開int ary[160] -> 看ary[1600]
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0xF0000 | ary[0]在這裡。 | 0 |
0xF0004 | ary[1]在這裡。 | 0 |
.... | ... | ... |
0xF003C | ary[159]在這裡。 | 0 |
0xF0040 | 還沒用到 | ??? |
... | ... | ... |
0xF0400 | 系統的祕密>///< | 不給尼看>///< |
因為程式沒幫你開到0xF0400,
只要那邊有系統的祕密/還沒規劃,看了就會吃RE!
小小總結
- 因為陣列大小吃RE的原因就是你存取了一個系統沒預料到的地址。
- 陣列的記憶體空間是連續的。
#include <iostream>
int ary[160];
int is_admin = 0;
int main(){
int i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin){
std::cout << "How did you do that?\n";
}
}
你要輸入什麼才可以讓程式輸出
"How did you do that" 呢?
Practice 0x02 - Global Flag
#include <iostream>
int is_admin = 0;
int ary[160];
int main(){
int i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin){
std::cout << "How did you do that?\n";
}
}
你要輸入什麼才可以讓程式輸出
"How did you do that" 呢?
Practice 0x03 - Global Flag
Practice 0x04 - Struct Corrupt
#include <iostream>
#include <cstring>
struct person{
char name[16];
int is_admin;
};
int main(){
char tmp[16];
struct person peipei;
peipei.is_admin = 0;
std::cin >> tmp ;
strcpy(peipei.name,tmp);
std::cout << "Hi! " << tmp << std::endl;
if(peipei.is_admin){
std::cout << "Wow! Peipei is so Dian!" << std::endl;
}
}
你要輸入什麼才可以讓程式輸出
"Wow! Peipei is so Dian!" 呢?
剛剛的Practice 0x03是一種典型的buffer overflow (BOF)攻擊喔!
為什麼可以征服宇宙?
如果你有程式碼就可以任意改值!無限血量無限金幣...
Challenge 1 - long long strike
#include <iostream>
long long ary[160];
int is_user=0;
int is_admin=0;
int main(){
long long i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin == 1){
std::cout << "Why Peipei is so dian?\n";
}
}
你要輸入什麼才可以讓程式輸出
"Why Peipei is so dian?" 呢?
0x00000001????????
例如 0x00000100000000 =
4294967296 (輸入4594967296即可。)
int 和 long long
怎麼存的?
表示成二進位制後逆著寫。
詳情請查詢 little-endian
(big-endian 就是順著寫)
0x00000001????????
longlong 存成
????????10000000
is_user 存 ????????
is_admin 存 10000000
-> is admin 是 00000001 = 1 (逆著)
Challenge 2 - Xross Frame
#include <iostream>
void put_something(){
int ary[1]={0}, i, x;
ary[i] = x;
}
bool can_access(){
int is_admin = false;
put_something();
return is_admin;
}
int main(){
if(can_access()){
std::cout << "Why Peipei is so dian?\n";
}
}
你要輸入什麼才可以讓程式輸出
"Why Peipei is so dian?" 呢?
Homework
pointer (2020 sprout)
By Arvin Liu
pointer (2020 sprout)
Teaching slide - pointer in C
- 1,850