2018/05/10 - C++ Bus error v.s Segmentation fault

緣起

今天在當C++程式設計的助教時,老師課堂正在教 struct tm 以及 asctime 的用法,然後請大家寫一份作業 - 作業內容就是把自己的生日塞到 struct tm 的 member 裡面,並用 asctime 印出來。

結果,剛開始學 struct 的大一同學在練習的時候,很多人用下面的寫法 :

#include<iostream>
#include<ctime>
using namespace std;

int main(){
    struct tm * timeinfo;

    timeinfo->tm_year = 94;
    timeinfo->tm_mon = 8;
    timeinfo->tm_mday = 23;
    timeinfo->tm_sec = 56;
    timeinfo->tm_min = 34;
    timeinfo->tm_hour = 12;
    timeinfo->tm_wday = 5;

    printf( "I was born at : %s" , asctime(timeinfo));
    return 0;
}

然後,就噴了 Bus error. 而另外有人則是將 struct tm * timeinfo; 改成 struct tm * timeinfo = NULL;,這樣的結果一樣會有Error,但卻是 Segmentation falut ! 到底為什麼會不一樣呢? 只是差一個 NULL 怎麼訊息就不一樣了呢 ?

為什麼這段程式會產生 Bus error ?

由上面的範例程式可以很明顯的看出, timeinfo 是一個 uninitialized pointer to struct tm。我們只知道 timeinfo 這個 pointer 對應的 data type,但是到底指向哪一個 struct tm 在這個程式裡面並沒有說明。

也就是說,這個 pointer 根本沒有拿出箭頭指向任何地方。

但是,程式裡面卻使用 timeinfo->tm_year (等同於 (*timeinfo).tm_year ) 這樣的語法,呼叫了 timeinfo 這個 pointer 所指到的記憶體位置內的 struct tm ,並呼叫 struct tm 裡面的成員 tm_year

我們已經知道 timeinfo 並沒有指向任何地方,那怎麼可能做 deference 呢 ? Compile 的時候沒有問題,但是執行時就會發現這樣的程式碼導致了 Bus error.

原因可以由下方對 Bus error 的解釋得到解答。

What is Bus error ?

在這篇 Stack Overflow : What is a bus error? 1 的討論中,有人是這麼解釋的 :

A bus error is trying to access memory that can’t possibly be there. You’ve used an address that’s meaningless to the system, or the wrong kind of address for that operation.

也就是說,我們去存取 根本不存在於此程式中的記憶體

由上面程式的例子而言,timeinfo 根本不指向任何位置,而我們又去存取這個不存在於這個程式的記憶體,因此 OS 偵測到了我們試圖去存取 根本不存在 或是 其他 Process 的記憶體位置 ,因此強制中斷此程式、噴了 bus error

注意 : 此時 timeinfo 可能是 任何的位置 ,完全無法預期。

為什麼範例程式程式會產生 Segmentation falut ?

我將上述的範例程式稍微修改 :

#include<iostream>
#include<ctime>
using namespace std;

int main(){
    struct tm * timeinfo = NULL;  // 只修改了這行,加了一個 NULL 當作 Init value

    timeinfo->tm_year = 94;
    timeinfo->tm_mon = 8;
    timeinfo->tm_mday = 23;
    timeinfo->tm_sec = 56;
    timeinfo->tm_min = 34;
    timeinfo->tm_hour = 12;
    timeinfo->tm_wday = 5;

    printf( "I was born at : %s" , asctime(timeinfo));
    return 0;
}

與之前範例程式的唯一差別就是 struct tm * timeinfo = NULL; 這行宣告了,但結果卻不是 Bus error 而是 Segmentation falut, 這細微的差別到底產生了什麼變化 ?

What is Segmentation falut ?

一樣在這篇 Stack Overflow : What is a bus error? 的討論中,有人是這麼解釋的 :

A Segmemtation falut is accessing memory that you’re not allowed to access. It’s read-only, you don’t have permission, etc…

另一篇文章 2 是這麼說明的 :

Segmentation Fault (also known as SIGSEGV and is usually signal 11) occur when the program tries to write/read outside the memory allocated for it or when writing memory which can only be read.

也就是說,我們去存取 我們沒有權限或不應該存取的記憶體

以上面的程式而言,struct tm * timeinfo = NULL; 宣告了 timeinfo 這個 pointer 指到了一個空的位置 ( 和 Uninitialized Pointer 沒有指向任何地方是不一樣的! 3 )。

可想而知,這個空的位置(NULL)裡面根本是空的,那又怎麼會有 struct tm 讓我們存取呢? 因此我們動到了不該存取的記憶體位置,產生了 Segmemtation falut.

自己的總結

Bus error : 存取根本 不存在於此程式 的記憶體位置

Segmentation falut : 存取 不該存取 的記憶體位置,如上述的 NULL

不存在 (Uninitialized Pointer)NULL (NULL Pointer) 的差別在於是否有向程式交代這個 pointer 的行為。指向 NULL 表示程式是目前想讓 pointer 先空著;但 Uninitialized Pointer 卻是完全讓程式無所適從,而產生了非預期的行為。

Reference

1

Stack Overflow - What is a bus error? : https://stackoverflow.com/questions/212466/what-is-a-bus-error

2

Segmentation Fault (SIGSEGV) vs Bus Error (SIGBUS) : https://www.geeksforgeeks.org/segmentation-fault-sigsegv-vs-bus-error-sigbus/

3

Pointers - Invalid pointers and null pointers : http://www.cplusplus.com/doc/tutorial/pointers/

[4] : What Does Null Mean in Computer Programming? : https://www.thoughtco.com/definition-of-null-958118