Re: [問題] 關於結構內的指標
※ 引述《aresnmars (哎喲)》之銘言:
: 嗨,您好:
: 雖然您的簽名檔有註解,但我仍冒失地寫站內信給您。
: 若您不介意,想和您簡單地請教了。
不好意思, 容我回你的信到板上
因為你的問題其實底層有一個更基本的觀念問題在裡面
這個觀念問題我覺得是很有可能其他人也會有的
(這也就是為什麼我的名片檔會說有問題直接上板發問
這樣除了其他人可以回答之外, 有一些東西也可以分享給其他有同樣問題的人)
: 關於:
: LPH66: 那邊討論的就是當 sizeof(int) == sizeo(int*) 02/09 03:13
: LPH66: 且結構成員之間沒有 padding 時的行為 02/09 03:13
: LPH66: 跟 nick5130 最一開始推文講的東西一模一樣 02/09 03:13
然後這裡我要先更正一下我自己
我推文時只簡單掃過他的回答說「p[1] 指向 s.p 指標」
就以為他講的跟 nick5130 一開始提的是同一個狀況
差在哪裡等一下提, 但這正是所謂「未定義行為」所要表達的事
(而且他的回答似乎有些問題, 同樣下述)
這裡的基本觀念就是: 對未定義行為追究原因是不切實際的
(講一句不客氣的話叫做: 天下本無事, 庸人自擾之)
你只要知道這裡在 ptr[1] = 3; 這一行發生了未定義行為就足夠了
(詳細一點說, 這裡的未定義行為是「對 ptr[1] 進行存取」)
還有一個更有趣的狀況, 在那個環境裡整支程式跑得完沒有 crash!
同一支程式因為環境不同有了多種不同的結果, 這就是未定義行為
對寫一般的程式而言, 未定義行為是必須極力避免的
除非你非常非常確定你的環境會如何執行某些動作再說
只是這種狀況在一般的程式裡是很少見的
===========================================
拉一條分隔線, 下面要來拆解了; 但為了強調上面講的觀念所以再寫一個警告:
※以下將會稍微詳細分析這幾行程式的可能行為
但不保證實際在哪個編譯器編譯或在哪台機器上執行就會有這些行為
未定義行為只代表編譯器可以方便行事, 不表示哪些行為是必然的
以下的分析不完全理解也不會對寫一般程式有什麼影響※
: 想請教您:
: - 結構之間的padding,指的是什麼?
: - 關於我找到的網頁: goo.gl/vhP3td 內
: 描述道:
: - p指針占用8字節 (p指標佔了8個bytes)
: - 修改了s.p指針高位的值
: - 修改了s.p指針的低位
: 如果sizeof(int) == sizeo(int*),那麼
: p指標不是佔了4bytes嗎?
: 高位、低位指的為何?
: (抱歉,我有點亂掉了。 希望您別介意回答我這樣的問題)
: 先謝謝您的熱心
上面提了未定義行為是在「對 ptr[1] 進行存取」
問題就在於到底對 ptr[1] 存取是存取到了什麼東西
警告裡有說未定義行為代表編譯器可以方便行事
也就是編譯器可以不管違規時會怎樣, 一律當做沒有違規來看
ptr[1] 在沒有違規時是會從 ptr 往後移動一個 int 大小, 從那裡取值
因此在這種狀況之下, 它存取到了什麼就隨實際上那個位址裡是什麼而定
回到這個結構:
struct S{
int i;
int *p;
};
這裡有一個 int 變數跟一個指標變數
但這兩個變數大小如何, 是如何配置在這個結構裡, 標準並沒有規定
(只有簡單規定了它們之間的相對關係, 詳細這裡不提)
=== 狀況一 ===
nick5130 一開始提的狀況是這樣的:
╭──────────── struct S s; ────────────╮
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ int i; │ int *p; │
└───────────────┴───────────────┘
↑
方便行文起見令這個 byte 在位址 7000, 這也是 ptr 一開始指向之處
int 和指標都是 8 byte
於是 ptr 往後移一個 int 大小就是 7008, 正好取到了 p 的所在地
p[1] = 3; 就會寫入一個 8 byte 的數值 3 進入位址 7008:
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 3 │
s.p = ptr; 把佔 8 byte 的位址 7000 放入 s.p 所在:
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 7000 │
s.p[1] = 1; 取出 s.p, 往後移 1 個 int, 放入 1
這造成一個 8 byte 的數值 1 寫入位址 7008:
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 1 │
最後 s.p[0] = 2; 取出 s.p, 這裡取到的是剛才寫入時蓋掉的 1 當做指標
也就是會把一個 8 byte 的數值 2 寫入位址 1, 造成 crash
=== 狀況二, 百度連結的說明 ===
那裡所提的狀況是這樣的:
╭──────── struct S s; ────────╮
7000 7004 7008
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ int i; │ int *p; │
└───────┴───────────────┘
int 是 4 byte, 指標是 8 byte
(這裡就是我搞錯的地方, 因為跟狀況一同樣地有 ptr+1 == &p
也就是「ptr[1] 指向 s.p 指標」
所以我把兩個狀況搞混了才會有那三行推文)
所以 ptr[1] = 3; 會把一個 4 byte 的數值寫入 ptr+1 即位址 7004:
7000 7004 7008
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │////// 1 ///// ??????????????│
注意到我後面四格打了問號, 表示那邊的值沒被動到, 還是未初始化的狀況
s.p = ptr; 把佔 8 byte 的位址 7000 放入 s.p:
7000 7004 7008
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 7000 │
s.p[1] = 1; 取出 s.p, 往後移一個 int, 寫入 1
即是把 4 byte 的數值 1 寫入位址 7004:
7000 7004 7008
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │////// 4 ////// **************│
右邊 **** 的部份是代表那是「位址 7000」這個 8 byte 數值的後四 byte
這四 byte 的內容究竟是什麼也是跟環境有關的
這裡有個專有名詞叫 little-endian, 詳細自行 google
這裡我只提由此衍生的所謂「低位」「高位」這兩個名詞
它們命名的來源跟 little-endian 有關
而在這裡它們分別指這個 8 byte 數值的前四 byte 和後四 byte
「修改了低位」就表示前四 byte 被修改了
這裡就是我說他的回答有點問題的地方
因為 s.p=ptr; 並不是讓 s.p 指向自己, 而是指向結構開頭
回答的人誤以為 s.p[1] 修改到的是 7008 開始的 4 byte
總之, 寫進去的 1 跟被留下來的位址的「高位」部份組合成了一個不知指向何處的指標
因此 s.p[0] = 2; 把 4 byte 數值 2 存入這個未知位址, 造成 crash
=== 狀況三, 神奇的不會 crash 的狀況 ===
這個狀況是這樣的:
╭──────────── struct S s; ────────────╮
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ int i; │<< padding >> │ int *p; │
└───────┴───────┴───────────────┘
int 還是 4 byte, 指標還是 8 byte
但兩個成員之間有一塊空間是誰都用不到的
這個即是所謂「結構成員之間的 padding」
關於為什麼會有 padding 這回事容我引一篇稍微不太正經的文章:
https://www.ptt.cc/bbs/Touhou/M.1337781212.A.3CB.html
(文章代碼(AID): #1FlElSFB (Touhou) [ptt.cc])
雖然不太正經但概念有到, 所以 XD
(如果要正經一點的解釋, 專有名詞是 alignment (對齊), 也請自行 google)
在這種狀況下, ptr[1] = 3; 取出 ptr, 往後移到一個 int 到位址 7004
所以 4 byte 的數值 3 就寫入這個地方
但它正好是 padding 所在的地方, 所以就變成這樣:
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 3 │??????????????????????????????│
s.p = ptr; 把位址 7000 放入 s.p 所在:
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 3 │ 7000 │
s.p[1] = 1; 取出 s.p, 往後移一個 int, 寫入 1
所以同樣把 4 byte 的數值 1 寫入位址 7004
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 4 │ 1 │ 7000 │
s.p[0] = 2; 取出 s.p, 寫入 2 在那位置
7000 7004 7008 7012
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 2 │ 1 │ 7000 │
程式執行完畢, 沒有 crash!
==========================
上面只是其中三種狀況
這還是對編譯器做了假設說對違規的東西當做沒違規來編, 編成怎樣不管
(甚至其實狀況三還是有可能 crash
只要那塊沒人管的地方被寫入時有被抓到就有可能)
除此之外還有一種我想得到的可能是當編譯器強制插入邊界檢查時
這種狀況之下 p[1] = 3; 會觸發邊界檢查失敗
至於是編譯失敗還是執行期 crash 則都有可能
這就跟結構的組織如何完全無關了
==========================
所以再講一次: 以上這一大串分析不全了解也沒差, 知道很可能不照你想的做事就好
未定義行為可能會炸可能不會炸, 會炸的方式也可能各有不同
除非你是做一些跟系統底層相關的東西不然沒必要去全盤了解原因
以上
--
將很小又單純的命令《Code》組合成函數《Function》。函數累積成更大更方便的元件《
Parts》,成為程式《App》。接著進行動態結合,相互通訊,打造出服務《Service》。
李奧納多知道,要得到結果,就必須持續進行非常單純的作業。為了展現出匹敵巨大建築
的技術,現在非得將面前的碎片組合起來。
知道這條路多麼遙遠的人,叫做極客《Geek》。
將這份尊貴具體呈現的人,叫做駭客《Hacker》。 --記錄的地平線 Vol.9 p.299
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 180.177.29.238
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1486596378.A.3B5.html
推
02/09 07:51, , 1F
02/09 07:51, 1F
推
02/09 08:57, , 2F
02/09 08:57, 2F
→
02/09 10:46, , 3F
02/09 10:46, 3F
推
02/09 13:06, , 4F
02/09 13:06, 4F
推
02/09 15:57, , 5F
02/09 15:57, 5F
→
02/09 15:57, , 6F
02/09 15:57, 6F
推
02/09 16:42, , 7F
02/09 16:42, 7F
推
02/09 16:43, , 8F
02/09 16:43, 8F
推
02/09 19:15, , 9F
02/09 19:15, 9F
推
02/09 20:46, , 10F
02/09 20:46, 10F
推
02/09 20:55, , 11F
02/09 20:55, 11F
推
02/09 21:06, , 12F
02/09 21:06, 12F
推
02/10 04:49, , 13F
02/10 04:49, 13F
推
02/10 11:27, , 14F
02/10 11:27, 14F
推
02/10 16:15, , 15F
02/10 16:15, 15F
推
02/10 16:39, , 16F
02/10 16:39, 16F
推
02/10 17:18, , 17F
02/10 17:18, 17F
推
02/10 17:31, , 18F
02/10 17:31, 18F
推
02/10 18:48, , 19F
02/10 18:48, 19F
→
02/10 18:48, , 20F
02/10 18:48, 20F
推
02/10 19:54, , 21F
02/10 19:54, 21F
→
02/10 19:56, , 22F
02/10 19:56, 22F
→
02/10 20:01, , 23F
02/10 20:01, 23F
推
02/10 20:12, , 24F
02/10 20:12, 24F
→
02/10 20:13, , 25F
02/10 20:13, 25F
推
02/10 20:25, , 26F
02/10 20:25, 26F
→
02/10 20:28, , 27F
02/10 20:28, 27F
→
02/10 20:31, , 28F
02/10 20:31, 28F
推
02/10 20:34, , 29F
02/10 20:34, 29F
→
02/10 20:35, , 30F
02/10 20:35, 30F
→
02/10 20:44, , 31F
02/10 20:44, 31F
→
02/10 20:46, , 32F
02/10 20:46, 32F
→
02/10 20:47, , 33F
02/10 20:47, 33F
→
02/10 20:48, , 34F
02/10 20:48, 34F
→
02/12 23:49, , 35F
02/12 23:49, 35F
→
02/12 23:50, , 36F
02/12 23:50, 36F
→
02/12 23:50, , 37F
02/12 23:50, 37F
→
02/12 23:51, , 38F
02/12 23:51, 38F
→
02/12 23:51, , 39F
02/12 23:51, 39F
推
02/13 18:03, , 40F
02/13 18:03, 40F
推
02/19 10:36, , 41F
02/19 10:36, 41F
推
02/19 18:06, , 42F
02/19 18:06, 42F
推
02/25 21:45, , 43F
02/25 21:45, 43F
推
03/27 13:02, , 44F
03/27 13:02, 44F
討論串 (同標題文章)