顯示具有 C語言 標籤的文章。 顯示所有文章
顯示具有 C語言 標籤的文章。 顯示所有文章

2017年5月21日 星期日

[C語言] 從錯誤中學習 - 錯誤訊息 Segmentation fault(Core Dump) 到底是什麼?

之前在作業鬼打牆時期的時候,一直遇到編譯器吐出這個錯誤

Segmentation fault(Core Dump)

雖然在作業寫不出來而且死線逐漸逼近的時候,內心十分火大

但是看到這個不熟悉的詞彙

第一個直覺就是利用 Google 搜尋這個關鍵字

於是耐著性子 Google 它

發生原因


首先,找到了成大王紹華同學的 晶心科技實習面試  這篇文章
(先謝謝王同學樂意分享,造福後人,也謝謝幕後推手 Jserv )

提到了發生的原因
當記憶體存取到超出程式可用的範圍時,或是去寫入 read-only 的記憶體區段,就會產生 segmentation fault 的錯誤。像是宣告 100 個 entry 的 array,你卻去讀取第 300 個,就可能超出記憶體範圍或者嘗試去讀取 NULL 的 pointer。
ps. 額外小補充,在 interrupt v.s exception  這段也提到
interrupt 是由硬體或軟體所送出的訊號(signal),要求 CPU 即時處理該事件,此時 CPU 會儲存當前狀態,並執行 interrupt,執行結束後便返回原本的狀態。 而 exception 也為 interrupt 的一種,但是由軟體產生的中斷,且只在特定情況下才被稱為 exception,像是程式無法掌控的狀況,如 division by zero, invalid memory access,所以 segmentation fault 也為 exception 的一種。 

另一篇則是 stackoverflow 上的文章 What is a segmentation fault?

發生原因寫得相當簡潔:
Segmentation fault is a specific kind of error caused by accessing memory that “does not belong to you.” 
(當存取了不屬於你的記憶體時,就會發生 Segmentation fault )
那為什麼有這個機制存在呢?
It’s a helper mechanism that keeps you from corrupting the memory and introducing hard-to-debug memory bugs.
 (是一個幫助程式開發者把管理記憶體的工作變簡單的機制,避免讓記憶體裡的資料變得一團亂,並且難以除錯。所以其實錯誤訊號是好人,而不是讓大家不能繼續撰寫程式的壞傢伙)

所以當出現了 Segmentation fault 錯誤時,通常是自己撰寫的程式不當的存取記憶體!

常見原因


其中一篇回答提到 常見的發生原因 像是:

1. 用一個尚未初始化、已經釋放記憶體的變數或是指標
2. 試圖寫入只能讀取的記憶體區段
3. scanf() 存某變數資料的數值時,忘記在該變數前加上取址符號&
4. 使用 I/O的 printf()、scanf() 時,輸入了不正確的 Format specifier (%s, %c etc)


個人心得:

Objective - 學到了Segmentation fault

Reflective - 寫 c 程式的時候,時常遇到這種錯誤,這次把錯誤發生的原因記錄下來

是為了避免未來又發生一樣的錯誤

雖然死線逐漸逼近的時候,內心十分惱火

但是耐著性子把原因找出來並解掉才是最快解決他的方法

套一句沂詰的話,如果一開始就想好,就不會有這個錯誤發生

既然知道了這個錯誤,請記取教訓,節省並保護自己的寶貴資產-時間

Interpretive - 決習系統別輕易使用貪婪演算法來做出選擇,除非問題十分簡單直觀,不然後續造成的錯誤需要更多時間去填坑

Decisional - 尤其常見原因的第4點,複習過使用文件,再去使用它,可以節省往後更多時間

(千萬不要選擇看似當下省時間,之後卻帶來無窮時間黑洞的方案)

2017年4月8日 星期六

[C語言] I/O 常見錯誤 : scanf() 篇

scanf() 常見問題


結論: 別再使用 scanf( ),請改用或是fgets( ) 或是 getline( ),再去作字串分割。麻煩,但是你清楚輸入字串裡面有什麼

上述說法可以參考: Scanf and loops

奇怪現象


在寫演算法作業優化的時候

發現一件事情

就是我在使用scanf() 來決定我要輸入幾筆測資

while loop 它竟然會 直接跳過 下面要求我輸入字串的指令

read = getline(&line, &len, stdin);

而直接輸出結果

然後輸出的字串是沒有顯示在螢幕上,但是字串長度是1,結果如下兩行所表示


line =
line length = 1

以下是範例程式  (2017/4/10 已修正亂碼錯誤)

//------------------- example code --------------------
#include <stdio.h>
#include <string.h>

int main(){ 
    int i = 0;
    int p;
    int dummy = 0;

    scanf("%d",&dummy);
    while( read != -1){
        //readline
        puts("enter a line");
        read = getline(&line, &len, stdin); 
        printf("line = %s", line);
        printf("line length = %zu\n", read); //%zd z coresponding to size_t type variable
        puts(line);

        if(i == dummy-1){ 
            printf("dummy = %d\n",dummy);
            break;
        }
        i++;
    }
    return 0;
}
//------------------- example code --------------------

後來在paslab的大家給予專業建議 外加 茫茫的stackoverflow的問題海中找到了答案

背後原因


Scanf skips every other while loop in C
When you read keyboard input with scanf(), the input is read after enter is pressed but the newline generated by the enter key which is not consumed by the call to scanf(). 
That means the next time you read from standard input there will be a newline waiting for you (which will make the next scanf() call return instantly with no data).

上面提到一件重要的事情,雖然 scanf("%d",&var1) 這行

會抓取標準輸入(standard input, 縮寫為stdin)的正整數來指定給變數 var1

如下面所說明的:
The standard I/O library provides a simple and efficient buffered stream I/O interface.
At program start-up, three streams shall be predefined and need not be opened explicitly: standard input (for reading conventional input), ...
--- STDIO(3) Linux Programmer's Manual

但是按下 Enter 鍵所產生的換行符號卻會留在緩衝區內(buffer)

所以 getline() 中負責抓取字元的 pointer

在緩衝區抓到的殘留字元就是按下 Enter鍵所輸入進的換行字元 

因此就產生換行字元直接餵給 getline(),執行時直接跳過的現象

這邊有鄉民驗證 ASCII code 的值為10,對應的按鍵不意外的就是換行的Enter鍵


還沒有概念? 可以看下面的圖解說明

圖解說明


如果用圖片來說明的話,以下圖片引用自 linux C标准库IO缓冲区--行缓冲实现 图解

如下圖一所示,當兩個指標 _IO_read_ptr  還沒與 _IO_read_end 重合時

表示緩衝區內還有內容可以讀取


圖一

後續的輸入會直接從緩存區裡面抓


圖二

之後兩者指向同一個位置的時候,才會停止自動抓取

解決方案


在不使用 getline()、fgets() 的情況下

要解決這個問題有兩個方法,但是概念一樣都是清空緩衝區

1. 在scanf() 後面加入一行 getchar() 把換行字元抓出來到其他地方

(注意,方法2 非正式規範!)

2.  用 fflush 清空輸入 input 的緩衝區( 即stdin的緩衝區)
 
一行可以處理  fflush(stdin);

補充知識


fflush 錯誤觀念澄清  : 正式規範是定義給  stdout 使用,不是 stdin 

fflush: flush a stream
In cases where a large amount of computation is done after printing part of a line on an output terminal, it is necessary to fflush(3) the standard output before going off and computing so that the output will appear.

觀念澄清1

 Q: How can I flush pending input so that a user's typeahead isn't read at the next prompt? Will fflush(stdin) work?

A: fflush is defined only for output streams

觀念澄清2

关于fflush(stdin)清空输入缓存流(C/C++) 新手必看!!
也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,把输入缓冲清空掉不就行了?” 然而这是错的! C和C++的标准里从来没有定义过 fflush(stdin)。 也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?” 的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲, 但是并非所有编译器都要支持这个功能(linux 下的 gcc 就不支持), 因为标准中根本没有定义 fflush(stdin)。

關於fflush的使用

Q: If fflush won't work, what can I use to flush input?


關於記憶體 - 緩衝區(buffer),資料是存在電腦的RAM (Random Access Memory) 中


關於換行符號

What is the newline character in the C language: \r or \n?

心得


從上次的演算法作業一學到一件事情,就是程式永遠都會按照目前編譯規範執行

如果發生預期以外的輸出結果,通常是因為疏忽使用規則而造成意外的結果

所以建議往後撰寫完時,務必一段一段的測試,並且有意外情況的時候,先看C使用手冊

The GNU C Reference Manual

再發問

---------------------------------------------
ps. 以上說明文件屬於 C89 規範

2017年4月6日 星期四

[C語言] 指標(pointer)觀念複習

指標,相當於告訴我們某個變數它的住址在哪,而該變數就住在標有那個住址的記憶體空間

(在 指標與記憶體位址 中提及 :變數(Variable)提供具名稱的記憶體儲存空間,(儲存資訊

包含)一個變數關聯(的)一個資料型態、儲存的值與儲存空間的位址值。所以我們常見的變數

其實也是有包含儲存位址的資訊)

常用符號


符號&,叫作取址運算子(Address-of operator),它可以用來取得變數所在的"記憶體位址"

符號*  ,叫作取值運算子(Dereference operator),它可以用來取得變數儲存的"數值"

宣告方式


根據指標的宣告方式,不同的資料型別的指標,宣告方式有些微不同,像是:

int *ptr1;  or int* ptr;
float *ptr2;
char *ptr3;

備註:這邊的*號是指宣告一個指標變數,*靠左靠右都可以,但是

int* ptr1, ptr2;

只有ptr1是指標變數,ptr2是整數變數。若都要宣告,則可以使用

int *ptr1,*ptr2;


使用細節


不像我們可以直接對變數進行操作,因為指標儲存的資訊是變數的位址資訊

如果我們想透過指標對變數進行操作,我們必須透過別的方式才行

這時就要提到指標的另一個特性:Dereference 提取

透過 * (dereference operator) 這個取值運算子,可以用來提取變數資訊

例如

int *ptr1 = &var1;

意思是,原本 ptr 這個指標變數所存的是變數 var1 的位址

*ptr


在前面加了*符號,則能夠將var1的數值給取出來


指標與陣列


觀念澄清: char a[] 跟 char *a 是一樣的嗎??? 


答案: 是完全不同! 指標跟陣列雖然有相似的地方,但是本質上是不一樣的東西

char a[] = "hello";
char *p = "world";

一張圖勝過千言萬語,看下面的圖就能快速明白兩者的差別




Q: But I heard that char a[] was identical to char *a.

Q: If they're so different, then why are array and pointer declarations interchangeable as function formal parameters?


指標與字串


演算法作業2需要同時處理指標與字串

來紀錄一下實作的細節部份

前面是getline()所需要使用的基本參數

char *line = NULL; //string pointer
size_t len = 0;    //unsigned
ssize_t read = 0;  //signed

line 儲存的是 line[] 的起始位址

這邊則是由 getline() 將字串讀入

read = getline(&line, &len, stdin);

完整的程式碼

//---2017/4/6  #c getline() 使用方法---
#include <string.h>

char *line = NULL; //string pointer
size_t len = 0;    //unsigned
ssize_t read = 0;  //signed

int main(){ 

    read = getline(&line, &len, stdin); 
    while( read != -1){
        for(int i=0;i<read-1;i++){
     printf("line+%d = %c\n",i,*(line+i));  //一個個的印出line指標指到的字串字元
        };
    }
free(line); //釋放記憶體
}

如果需要一個個的把字元指針 line 所指的字串字元給印出來,這邊需要這樣寫

printf("line+%d = %c\n",i,*(line+i));

這邊的

*(line+i)

取值運算子*取出的是字元指針 line 指向第 i 個元素位址儲存的字元

printf() 的format specifier 需要選用c,寫成c code就變成

%c

如果是%s,它會直接印出一整行,而且後面需要放的是指向字串的指針 line

不是*(line+i)

可以參考這篇 Can a pointer to a string be used in a printf?

底下也有鄉民提到%s接受的格式是字元指針(char *)形式

The "%s" format specifier for printf always expects a char* argument.


segmentation fault(core dump)

如果這樣寫

printf("line+%d = %s\n",i,*(line+i));

gcc編譯過的話,執行到這行時會得到錯誤資訊 segmentation fault(core dump)

原因是因為,%s這邊想要讀取的資料格式是資料的位址

所以我們這邊要放入的變數應該是字元指針變數line

可是今天我們輸入的資料是另一個數值*(line+i),當作字串位址丟進去

程式會訪問到其他不該訪問的程式記憶體位置

於是就發生了segmentation fault(core dump)

----------------------------------------------------------------------------
思考題 Q: *line+(i-1) vs *( line+(i-1) ) 差別在哪?

2015年12月5日 星期六

關於C,與如何上手








选择一门编程语言,“为什么而学”这个目的是最重要的,目的不明确就没法学好。


內文如下 : 


来自: 苏子佩



-----------------------------------------------------------------------------------------------------------------------

程式寫作與一般寫作類似的地方在於,一開始寫作的作品常容易犯一些邏輯、冗贅等寫作的

錯誤。透過閱讀同儕與大師的文章(程式碼),開始能夠發現自己的不足之處,開始能夠撰寫出

文字精鍊,言簡意賅的文章出來。

2013年1月5日 星期六

CNC 系統 Debug 前哨站

今天Debug VC++說 :
第一個可能發生的例外狀況類型
'System.NullReferenceException' 發生於 AlexCNC.exe
類型 'System.NullReferenceException' 的未處理例外狀況
發生於 AlexCNC.exe 
其他資訊: 並未將物件參考設定為物件的執行個體。 
上網查過之後發現是 Run time Error 的一種

MSDN論壇的討論串
http://social.msdn.microsoft.com/forums/zh-TW/233/thread/182a8bc2-d50b-49a6-9a46-f96ab56df779
/* 載入prettify的autoloader */ /* 載入JQuery */