Head First C學習日誌 第六章 最高機密 二叉樹和valgrind工具

程序會從根節點開始提問,其左右子樹爲疑犯名字或另一個問題。先看數據結構:node

typedef struct node {
	char *question;
	struct node *no;
	struct node *yes;
} node;

一個遞歸結構,內容很簡單,一個char*指針,兩個node指針。python

節點的建立函數:數據結構

node *create(char *question) {
	node *n = malloc(sizeof(node));
	n->question = strdup(question);
	n->no = NULL;
	n->yes = NULL;
	return n;
}

先分配空間,而後拷貝question字符串常量,兩個node指針置空。函數

節點的銷燬函數:this

void release(node *n) {
	if (n) {
		if (n->no)
			release(n->no);
		if (n->yes)
			release(n->yes);
		if (n->question)
			free(n->question);
		free(n);
	}
}

從輸入節點開始遍歷,遞歸銷燬其子樹,釋放n->question字符串,最後釋放n。spa

一個通用的判斷輸入y/n的函數:
指針

int yes_no(char *question) {
	char answer[3];
	printf("%s? (y/n):", question);
	fgets(answer, 3, stdin);
	if (answer[sizeof(answer) - 1] == '\n')
		answer[sizeof(answer) - 1] = 0;
	return answer[0] == 'y';
}

若是輸入y返回1,輸入其餘字符返回0。code

以上代碼比較簡單,二叉樹的建立主要在主要在主函數裏。遞歸

這段代碼比較長,分開貼:homebrew

char question[80];
	char suspect[20];
	node *start_node = create("Does suspect have a mustache");
	start_node->no = create("Loretta Barnsworth");
	start_node->yes = create("Vinny the Spoon");

	node *current;

建立接收用戶輸入的question和suspect字符串。

建立根節點,並初始化一個問題,「疑犯有鬍子麼」。

將根節點的兩個子樹初始化爲「Loretta Barnworth」和「Vinny the Spoon」。

定義一個current指針,用作活動指針,鏈接二叉樹。

外層循環:

do {
}while(yes_no("Run again"));

至少執行一次,若是用戶在yes_no函數輸入y,則重複執行。

循環體:

current = start_node;
		while (1) {
		......
		}

將current指向起始節點,並執行while(1)循環的內容,一個if else條件判斷,第一個if:

if (yes_no(current->question)) {
				if (current->yes)
					current = current->yes;
				else {
					printf("SUSPECT IDENTIFIED\n");
					break;
				}
			}

yes_no打印問題(或疑犯名)並要求用戶輸入,若是用戶選擇y,則進入子條件判斷:當前節點current->yes是否存在,若是存在,將current=current->next,會進入下一次循環,若是不存在current->yes,則表示當前節點就是疑犯節點(疑犯節點沒有子樹),輸出「SUSPECT IDENTIFIED」,跳出本層循環

下一個條件,若是用戶在yes_no時輸入了n(本文中不輸入y一概認爲輸入n)

else if (current->no) {
				current = current->no;
			}

判斷該節點是否存在no子樹,若是存在,將current=current->no,進入下一次循環。

最後一個條件,在yes_no()時輸入了n,可是又沒有no子樹,表示咱們已存在的數據中,並無咱們想定位的疑犯信息,那麼會進入新建步驟:

else {
				/*Make the yes-node the new suspect name*/
				printf("Who's the suspect? ");
				fgets(suspect, 20, stdin);
				if (suspect[sizeof(suspect) - 1] == '\n')
					suspect[sizeof(suspect) - 1] = 0;
				node *yes_node = create(suspect);
				current->yes = yes_node;
				/*Make the no-node a copy of this question*/
				node *no_node = create(current->question);
				current->no = no_node;
				/*The replace this question with the new question*/
				printf("Give me a question that is TRUE for %s but not for %s ", suspect, current->question);
				fgets(question, 80, stdin);
				if (question[sizeof(question) - 1] == '\n')
					question[sizeof(question) - 1] = 0;
				/*while replacing,the old block for 'question' must be freed*/
				current->question = strdup(question);
				break;
			}

程序輸出:「Who's the suspect?」

輸入疑犯名字,而後進行如下幾步操做:

  1. 建立一個新節點(疑犯節點)yes_node

  2. 將當前節點(疑犯節點)current->yes=yes_node

  3. 建立一個新節點(疑犯節點),與當前節點問題(其實是疑犯名)相同,no_node,並將當前節點的current->no=no_node;注意,此時有兩個節點的question(疑犯名)相同,當前節點和以當前節點疑犯名命名的新節點。

  4. 輸入一個新問題,對你給出的新疑犯是true,對當前節點的老疑犯爲false

  5. 將當前節點的current->question(疑犯名)指向你新輸入的question的拷貝,strdup(question)。

程序最後release(start_node),銷燬整個二叉樹。

第一部分,二叉樹的動態建立到此爲止,接下來是用valgrind檢查內存泄漏。


用valgrind檢查內存泄漏:

安裝valgrind:brew install valgrind 就這麼簡單,強烈安利homebrew。

使用valgrind檢查:輸入命令 valgrind --leak-check=full ./spies

➜  spies valgrind --leak-check=full ./spies
==1165== Memcheck, a memory error detector
==1165== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1165== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==1165== Command: ./spies
==1165==
Does suspect have a mustache? (y/n):n
Loretta Barnsworth? (y/n):n
Who's the suspect? Johnny
==1165== Conditional jump or move depends on uninitialised value(s)
==1165==    at 0x100000D62: main (in ./spies)
==1165==
Give me a question that is TRUE for Johnny
 but not for Loretta Barnsworth He burns
==1165== Conditional jump or move depends on uninitialised value(s)
==1165==    at 0x100000E01: main (in ./spies)
==1165==
Run again? (y/n):n

注意必定要對初始化後的子樹作一些改動才能復現。

結果:

==1165==
==1165== HEAP SUMMARY:
==1165==     in use at exit: 42,915 bytes in 420 blocks
==1165==   total heap usage: 530 allocs, 110 frees, 50,093 bytes allocated
==1165==
==1165== 19 bytes in 1 blocks are definitely lost in loss record 8 of 82
==1165==    at 0x100008EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==1165==    by 0x1001ECDDE: strdup (in /usr/lib/system/libsystem_c.dylib)
==1165==    by 0x100000B57: create (in ./spies)
==1165==    by 0x100000C61: main (in ./spies)
==1165==
==1165== LEAK SUMMARY:
==1165==    definitely lost: 19 bytes in 1 blocks
==1165==    indirectly lost: 0 bytes in 0 blocks
==1165==      possibly lost: 0 bytes in 0 blocks
==1165==    still reachable: 4,096 bytes in 1 blocks
==1165==         suppressed: 38,800 bytes in 418 blocks
==1165== Reachable blocks (those to which a pointer was found) are not shown.
==1165== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==1165==
==1165== For counts of detected and suppressed errors, rerun with: -v
==1165== Use --track-origins=yes to see where uninitialised values come from
==1165== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 16 from 16)

致使內存泄漏的函數給咱們列舉出來了,書中給的例子裏結果中有行號,但是用的版本沒有,只有檢查這些函數在程序中的調用及其上下文

按照書中的解釋,第一次不對二叉樹做改動的狀況下,並無出現內存泄漏,所以create函數沒有問題,這也是一種定位方法,控制可變量。

問題出在current->question = strdup(question);由於在這步以前,current->question是不爲空的,它已經指向了某塊堆內存,如今給它新賦值,那塊內存就找不到了。因此咱們要在current->question = strdup(question)以前,先free(current->question)。

相關文章
相關標籤/搜索