*&*&*&*&*&指標

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