本週學習任務:《深刻理解計算機系統》第2章 信息的表示和處理(教材導讀)html
『問題一』:linux
課本P22提到:git
使用32位表示數據類型int,計算表達式200*300*400*500會得出結果-884901888數組
這個結果是如何獲得的呢?安全
『問題一解決』:函數
在計算器上分別模擬64位和32位計算機計算200*300*400*500的值:工具
對比能夠發現,在32位的機器上發生了溢出,只保留末32位,因此獲得了-884901888這個值。post
『問題二』:學習
課本P31程序中len爲size_t類型,觀察程序的功能發現len僅僅用在for循環的終止條件判斷中,爲何不直接用int類型呢?測試
『問題二解決』:
查閱資料瞭解到,size_t類型是一個基本的無符號整數的C / C + +類型, 它是sizeof操做符返回的結果類型, 該類型的大小可選擇。所以,它能夠存儲在理論上是可能的任何類型的數組的最大大小。 換句話說,一個指針能夠被安全地放進爲size_t類型(一個例外是類的函數指針,可是這是一個特殊的狀況下)。 size_t類型一般用於循環、數組索引、大小的存儲和地址運算。(百度百科)
由此得知,在標準C庫中定義的(或32位系統)爲unsigned int,在64位系統中爲 long unsigned int。不一樣平臺的size_t會用不一樣的類型實現,所以,使用size_t而非int或unsigned可以提高代碼的可擴展性。
在/usr/include/asm-generic路徑下的posix_types.h文件中,咱們能夠查詢到相關說明:
『問題三』:
課本P33提到:
對數值12345編碼,整型爲0x00003039,而浮點數爲0x4640E400
將十六進制模式擴展成二進制形式,能夠發現一個有13個相匹配的位的序列。是如何獲得的呢?
『問題三解決』:
12345的整數表示爲:
在IEEE 754 Calculator查看其浮點數格式爲:
這是由於,這兩種格式使用不一樣的編碼方法。針對整型數字,是按照咱們熟知的方法;而對於浮點數的處理方法有所不一樣。能夠看到,對於浮點數的編碼由三個部分組成:符號(sign)、尾數(significand)與階碼(exponent)。
12345的二進制表示爲[11000000111001],將二進制小數點左移13位,獲得規格化表示:12345=$1.1000000111001_2 x 2^13$。構造小數字段,須要去掉開頭的1,並在末尾添上10個0,獲得二進制表示[10000001110010000000000]。構造階碼字段,用13加上偏置量127,獲得140,二進制爲[10001100]。再加上符號位的0,即獲得二進制浮點數表示爲01000110010000001110010000000000。
『問題四』:
課本P65關於問題「編寫函數,判斷參數x和y相加是否產生溢出」給出了一個錯誤的示範(練習題2.31),這種方法錯誤之處在哪?
『問題四解決』:
練習題2.31給出的程序以下:
int tadd_ok(int x, int y) { int sum = x+y; return (sum-x == y)&&(sum-y == x); }
補碼加法會造成一個阿貝爾羣,因此不論是否有溢出,都知足sum-x==y且sum-y==x。
正確的方法爲:
int uadd_ok(unsigned x, unsigned y) //肯定無符號加法是否溢出 { unsigned sum = x+y; return sum >= x; }
int tadd_ok(int x, int y) //肯定補碼加法是否溢出 { int sum = x+y; int neg_over = x < 0 && y < 0 && sum >=0; //判斷負數溢出 int pos_over = x >= 0 && y >= 0 && sum < 0; //判斷正數溢出 return !neg_over && !pos_over }
『問題五』:
課本P87練習題2.54的G項如何理解?
『問題五解決』:
回顧IEEE表示浮點數的方法,符號位(sign)位於二進制表示法的首位。乘法和除法結果的符號是由兩個參與運算的數字決定的。所以,即便d*d的結果可能溢出至+∞,但不影響其符號位。
『問題一』:
運行時提示段錯誤(核心已轉儲)的解決方法(以P38練習題2.11爲例)。
『問題一解決』:
編寫的程序以下,使用a^a=0
這一屬性完成數值交換:
#include <stdio.h> #include <stdlib.h> void inplace_swap(int *x, int *y); void reverse_array(int a[], int cnt); int main(){ int a[3] = {1, 2, 3}; int b[4] = {1, 2, 3, 4}; reverse_array(a, 3); reverse_array(b, 4); for(int i = 0; i <= 2; i++){ printf("%d ",a[i]); } printf("\n"); for(int j = 0; j <= 3; j++){ printf("%d ",b[j]); } printf("\n"); return 0; } void inplace_swap(int *a, int *b) { *b = *a ^ *b; *a = *a ^ *b; *b = *a ^ *b; } void reverse_array(int a[], int cnt){ int first, last; for(first = 0, last = cnt-1; first < last; first++,last++) { inplace_swap(&a[first], &a[last]); } }
編譯運行時,卻出現了這樣的問題:
查閱資料瞭解到,出現段錯誤(核心已轉儲)的緣由有如下幾種可能:
本程序出現錯誤屬於哪一種狀況呢?可使用gcc和gdb調試程序。
從結果看出,程序收到 SIGSEGV 信號,觸發段錯誤,並提示地址0x00000000004006df,在取指針內容進行運算時出現錯誤。
能夠經過man 7 signal查看SIGSEGV的信息:
由此排查,發現函數reverse_array()中循環的操做應爲first++,last--
。first++,last++
會形成數組越界,出現段錯誤提示。
經過man 7 signal,能夠看到SIGSEGV默認的處理程序(handler)會打印段錯誤信息,併產生 core 文件,由此咱們能夠藉助於程序異常退出生成的 core 文件中的調試信息,使用 gdb 工具來調試程序中的段錯誤。
默認狀況下是不產生 core 文件的,首先能夠查看一下系統 core 文件的大小限制:
經過ulimit -c 1024
命令,設置core文件的大小位1024kb。運行程序,發生段錯誤生成 core 文件;加載 core 文件,使用 gdb 工具進行調試:
一樣能夠顯示出錯誤信息。
核心轉儲文件(Core File)也是一種ELF文件,能夠經過readelf -h core
查看core文件的相關信息:
『問題二』:
經過對課本P54代碼進行測試,驗證補碼數進行符號擴展的方式。
『問題二解決』:
能夠看出,儘管-12345的補碼錶示和53191的無符號表示在16位字長時是相同的,但在32位字長時,-12345的十六進制表示爲0xFFFFCFC7,而53191的十六進制表示爲0x0000CFC7。這是使用最高位進行符號擴展形成的不一樣。
『問題三』:
課本P58練習題2.25爲何會出現段錯誤?如何改正?
程序以下:
float sum_elements(float a[], unsigned length){ int i; float result = 0; for(i = 0; i <= length-1; i++){ result += a[i]; } return result; }
『問題三解決』:
當length爲0時,因爲length是無符號的,因此將使用無符號運算。0-1結果爲UMax。對於任何數而言,都是小於UMax的,因此,將訪問數組a的非法元素,形成段錯誤。
『問題四』:
課本P86總結了int、float和double格式之間進行強制類型轉換時,程序改變數值和位模式的原則。試寫代碼測試一下。
『問題四解決』:
1.int→float:數字不會溢出,但可能被舍入。
在程序中使用斷言:
#include <stdio.h> #include <assert.h> float i2f(int a); //int轉float float i2f(int a){ return (float)a; } int main(){ assert(i2f(16777217)==16777217.0);//測試舍入 return 0; }
運行時卻出現了錯誤:
爲何會出現這種狀況呢?
int型的有效位數是31,而float型小數域的有效位只有23位,也就是說若是int a的二進制的有效位超過了24位,那麼float型的小數域的精度就不夠了。因而進行了舍入。
以16777217爲例,16777217=2^24 +1,所以需舍入爲2^24,代碼改成assert(i2f(16777217)==16777216.0);
就能夠了。
2.int/float→double:double有更大的範圍(也就是可表示值的範圍),也有更高的精度(也就是有效位數),所以可以保留精確數值。
3.double→float:範圍縮小,可能溢出成+∞或-∞;也有可能被舍入。
這裏舉一個溢出的例子:
assert(d2f(123.456789e100)==123.456789e100);
運行結果爲:
能夠看出,因爲float表示範圍和精度的限制,轉換過程形成了溢出。
4.float/double→int:向零舍入,或爲整數不肯定值。
如下斷言均成立:
assert(f2i(5.00)==5);//測試正常狀況 assert(f2i(1.99)==1);//測試向零舍入 assert(f2i(-1.99)==-1);//測試向零舍入
但若是不能爲該浮點數找到一個合理的整數近似值,就會產生整數不肯定(integer indefinite)值。好比:+1e10。
能夠看出,每一次的運行結果都是不一樣的。
代碼量截圖:
隊友:20155213
他的博客中值得學習的或問題:
他的代碼中值得學習的或問題:
結對照片:
結對學習內容:
上一週在編寫myod相關程序時,遇到了一些沒法解決(目前也正在研究)的問題,好比Linux下curse.h庫的使用等等。爲了更好地實現功能,隊友在個人思路的基礎上,又提出了不少寶貴的改進意見,幫助我解決問題,並寫了一篇博客進行總結。題目雖小,但咱們在不斷迭代修改的過程當中,受益不淺。
本週主要學習了課本第二章「信息的表示和處理」,深刻研究了計算機算術運算的特性等等,瞭解了能夠表示的值的範圍和不一樣算術運算的屬性,從而保證編寫的程序能在所有的數值範圍內正確工做,並且具備能夠跨越不一樣機器、操做系統和編譯器組合的可移植性。算術運算的數學屬性決定了稍不留神就會產生溢出等錯誤,算術溢出是形成程序錯誤和安全漏洞的一個常見根源,所以在編寫程序時要格外注意。
在學習課本內容的過程當中遇到了大量的公式,其推導過程讓人眼花繚亂。但耐心看完以後發現只不過是把直覺的認知轉化成了通過嚴格證實的推理,其實並不難理解;再佐以適當的練習,把實踐過程當中遇到的問題都弄明白,也就大體理解計算機算術運算的原則了。
好比本章末關於IEEE浮點表示的講解,在學習本章以前我有過大體的瞭解,一次是上學期編寫Java程序遇到的浮點數精度偏差問題,還有一次是暑假複習C語言格式化輸出函數printf。兩次都只是只知其一;不知其二,查了資料仍是滿頭霧水,此次天然在內心抵制,以爲浮點數的表示方法晦澀難懂。因此在學習時格外當心,一個字一個字讀,遇到不明白的就在IEEE 754 Calculator上模擬計算機的操做,或者動手驗證。一章學下來,也算對IEEE浮點數表示法有了最基本的感性和理性認識,下次再遇到浮點數的精度偏差問題,就能進行基本的分析,而不是百思不得其解,眉毛鬍子一把抓了。
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 20篇 | 400小時 | |
第一週 | 50/50 | 1/1 | 8/8 | 瞭解計算機系統、靜態連接與動態連接 |
第三週 | 451/501 | 2/3 | 27/35 | 深刻學習計算機算術運算的特性 |