中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
《C專家編程》學(xué)習(xí)筆記

Chapter 1

  1. char *cp;
  2. const char *ccp;
  3. ccp = cp;
  • ccp是有指向有const 限定符的char 的指針,cp是指向沒有限定符修飾的指針;左操作數(shù)具有右操作數(shù)的所有限定符(空),再加上自身的限定符(const);但是反過來賦值cp = ccp 就會違反賦值約束條件,就會產(chǎn)生編譯告警。
  • 而const float * 是指向具有const限定符的float類型的指針,也就是說const限定符是修飾指針?biāo)赶虻念愋停皇侵羔槺旧?。類似的,const char ** 也不是具有限定符的類型,它是指向有const 限定符的float類型的指針的指針,也就是說const 限定符是修飾指針的指針?biāo)赶虻念愋?;char ** 類型指向char *,const char **類型指向const char * 兩者是不相容的。
  • const 并不能把變量變成常量,在一個符號前面加上const 限定符只是表示這個符號不能被賦值,但也不能防止通過程序的內(nèi)部方法來修改這個值,const 最有用之處就是用它來限定函數(shù)的形參,這樣該函數(shù)將不會修改實參指針?biāo)傅臄?shù)據(jù),但其他的函數(shù)卻可能會修改它,這是const最一般的用法。
  1. const int limit = 10;
  2. const int * limitp = &limit;
  3. int i = 27;
  4. limitp = &i;
  • 這段代碼表示limitp是一個指向常量整形的指針,也就是說const 是修飾limitp指針指向的int類型。這個指針不能用于修改被指向的int類型,但是任何時候limitp本身的值卻可以改變。這樣limitp就指向了不同的地址。const int * a與 int const * a兩者沒有區(qū)別;int * const a 與前兩者不同,表示指針a的值不能修改,即a指向地址不能修改,但是可以修改a指向地址的內(nèi)容;前兩者表示不能通過指針a修改a所指向的內(nèi)容,但是a可以指向其他地址;---參考鏈接:https://www.cnblogs.com/wecode/p/6146739.html
  1. 有符號數(shù)二進制原碼,正數(shù),最高位符號位是0,其余部分是數(shù)值位;負數(shù),最高位符號位是1,其余部分是數(shù)值位;
  2. 有符號數(shù)二進制補碼,正數(shù)的補碼就是二進制原碼,負數(shù)的補碼是二進制反碼加1;
  3. 有符號數(shù)SWORD16的最大值是0x7FFF,第一位拿來當(dāng)做符號位;
  4. 有符號數(shù)的二進制反碼,正數(shù)的反碼與二進制原碼相同,負數(shù)的二進制反碼是二進制原碼除符號位外按位取反;
  5. 有符號數(shù)轉(zhuǎn)換成無符號數(shù),有符號正數(shù)原碼就是無符號正數(shù)原碼,有符號負數(shù)二進制原碼的補碼就是轉(zhuǎn)換成的無符號數(shù);
  6. 無符號數(shù)轉(zhuǎn)換成有符號數(shù),最高位不為1,無符號數(shù)的原碼就是有符號數(shù)的原碼,最高位為1,將此無符號數(shù)的原碼視為有符號數(shù)的原碼,除最高位即符號位負號外,最低7位作為數(shù)值位;
  7. 補碼=>原碼,符號位0 補碼就是原碼;符號位1 ,其余位取反后加1 是原碼

Chapter2

  1. //一種簡單的方法,使一段代碼第一次執(zhí)行時的行為與以后的執(zhí)行時不同
  2. generate_initializer(char * string)
  3. {
  4. static char separator = ' ';
  5. printf('%c %s \n', seperator, string);
  6. separator = ',';
  7. }
  8. static function turnip() {} //static 聲明表示在這個文件之外不可見
  9. static 在函數(shù)內(nèi)部,表示該變量的值在各個調(diào)用間一直保持延續(xù)性;
  10. extern 用于函數(shù)定義,表示全局可見(屬于冗余),用于變量表示它在其他地方定義;
  11. void 作為函數(shù)的返回類型表示不反悔任何值,在指針的申聲明中表示通用指針的類型,位于參數(shù)列表中表示沒有參數(shù);
  12. sizeof操作數(shù)是類型時兩邊必須加上括號,但操作數(shù)如果是變量則不必加括號;

針對最后一行,當(dāng)執(zhí)行i = 1,2;
賦值最運算符優(yōu)先級高于逗號,i 應(yīng)該是1,但是實際上i 賦值為1,接著執(zhí)行常量2的運算,計算結(jié)果將丟棄.最終i的結(jié)果是1而不是2.在if 和while 等后面需要一個布爾值的時候,& 和 |就被翻譯成&& 和 || ;如果在一般的表達式里,就被解釋成位操作符。
操作符的結(jié)合性,在幾個操作符具有相同優(yōu)先級時決定先執(zhí)行哪一個,賦值運算符具有右結(jié)合性,位操作符 & 和 | 具有左結(jié)合性。


不能返回指向局部變量的指針

  1. /* 將源文件的timestamp轉(zhuǎn)換為表示當(dāng)?shù)馗袷饺掌诘淖址?*/
  2. char * localized_time(char * filename)
  3. {
  4. struct tm *tm_ptr;
  5. struct stat stat_block;
  6. char buffer[120];
  7. /* 獲得源文件的timestamp, 格式為time_t */
  8. stat(filename, &stat_block);
  9. /* 把UNIX的time_t轉(zhuǎn)換為tm結(jié)構(gòu),里面保存當(dāng)?shù)貢r間 */
  10. tm_ptr = localtime(&stat_block.st_mttime);
  11. /* 把tm結(jié)構(gòu)轉(zhuǎn)換成以當(dāng)?shù)厝掌诟袷奖硎镜淖址?*/
  12. strftime(buffer, sizeof(buffer), '%a %b %e %T %Y',tm_ptr);
  13. return buffer;
  14. }

// 這段代碼的問題在于最后一行返回局部變量的指針,當(dāng)函數(shù)結(jié)束時,由于該變量已經(jīng)被銷毀,即當(dāng)控制流離開
//聲明自動變量范圍時,局部變量自動失效,無法知道這個局部變量指針?biāo)傅膬?nèi)容是什么?
解決辦法有
1.返回一個指向字符串常量的指針;
2.使用全局申明的數(shù)組,但是也要防止其他人修改這個全局數(shù)組;
3.使用靜態(tài)數(shù)組,將buffer聲明為static char buffer[120],這可以防止其他人修改這個數(shù)組只有擁有指向該數(shù)組的指針的函數(shù)才能修改這個靜態(tài)數(shù)組,但是函數(shù)的下一次調(diào)用將會覆蓋這個數(shù)組的內(nèi)容,另外,和全局數(shù)組一樣,大型緩沖區(qū)閑置不用是非常浪費內(nèi)存的;
4.顯式分配一些內(nèi)存,

  1. char * func() {
  2. char * s = malloc(120);
  3. ……
  4. return s;
  5. }

這個方法具有靜態(tài)數(shù)組的優(yōu)點,每次調(diào)用會創(chuàng)建一個新的緩沖區(qū),所以該函數(shù)以后的調(diào)用不用覆蓋以前的返回值。適用于多線程的代碼。缺點是要求程序員必須承擔(dān)內(nèi)存管理的責(zé)任,會增加內(nèi)存尚在適用就釋放或者內(nèi)存泄露的風(fēng)險;
5.最好的解決方法是要求調(diào)用者分配內(nèi)存來保存函數(shù)的返回值。為了提高安全性,調(diào)用者應(yīng)該同時指定緩存區(qū)的大小

  1. void func(char * result, int size) {
  2. ……
  3. strncpy(result, 'That'd be in the data segment, Bob', size);
  4. }
  5. buffer = malloc(size);
  6. func(buffer, size);
  7. ……
  8. free(buffer);

Chapter 3

C語言晦澀的聲明語句是一個缺點;
union與結(jié)構(gòu)體類似,但是內(nèi)存布局上存在關(guān)鍵性的區(qū)別,在結(jié)構(gòu)體中所有成員依次存儲,在union中所有成員的都從零開始存儲。這樣,每個成員的位置都重疊在一起:在某一時刻,只有一個成員正真存儲于該地址。union與struct結(jié)構(gòu)一樣,只是用union取代了關(guān)鍵字struct。union一般用來節(jié)省空間,也可以把同一個數(shù)據(jù)解釋成兩種不同的東西而不需要賦值或強制類型轉(zhuǎn)換

  1. union bits32_tag{
  2.         int whole; /*一個32位的值*/
  3.         struct {char c0, c1, c2, c3;} byte;/*4個8位的字節(jié)*/
  4. }value;

用enum完成的事都可以用#define來完成;默認情況下enum從整形值0開始,如果對某個標(biāo)識符賦值,那么緊隨其后的那個標(biāo)識符的值就比所賦的值大1。

enum Day {MON=1, TUE, WED, THU, FRI, SAT, SUN};

C語言聲明的優(yōu)先級規(guī)則
const關(guān)鍵字的后緊跟類型說明符,那么它作用于類型說明符。其他情況下,關(guān)鍵字作用于左邊緊鄰的指針星號*。
合理使用const關(guān)鍵字可以使編譯器自然地保護那些不希望被改變的參數(shù),防止其被無意的代碼修改。
優(yōu)先級從高到低依次是:
1.聲明中被括號括起來的部分;
2.后綴操作符:括號()表示這個是函數(shù),方括號[]表示這是一個數(shù)組;
3.前綴操作符:星號* 表示 “指向...的指針”
用優(yōu)先級規(guī)則來分析C語言聲明,char * const *(*next)();
1.首先找標(biāo)識符“next”,并注意到它被括號括住;
2.把括號里的東西作為一個整體,得出是“next是一個指向...的指針”
3.然后考慮括號外面的東西,在星號前綴和括號后綴之間作出選擇,
4.后綴運算符優(yōu)先級高于前綴運算符,右邊是括號,得出“next是一個函數(shù)指針,指向一個返回...的函數(shù)”
5.然后處理前綴運算符,得出指針?biāo)傅膬?nèi)容
6.把char * const 解釋指向字符的常量指針
總結(jié)就是,next是一個指針,指向一個函數(shù),函數(shù)返回另一個指針,該指針指向一個指向char 類型的常量指針
char *( * c[10])(int **p);聲明是c是一個數(shù)組,數(shù)組中每個元素是一個指針,這些指針都是具有(int **p)形參的指針函數(shù),函數(shù)返回一個指向char類型的指針,函數(shù)的形參(int **p) p是一個指針,指向一個指向int的指針;
 

  • typedef 并不創(chuàng)建變量,而是宣稱“這個名字是指定類型的同義詞”
  • void (*signal(int sig, void(*func)(int)))(int);分析這個聲明,signal前面有*結(jié)合后面的括號說明是函數(shù)且返回指針,參數(shù)是int和函數(shù)指針;返回的指針指向一個函數(shù),函數(shù)接收一個int類型參數(shù)并返回void。
  • 用typedef定義簡化這個函數(shù),分為兩步
  1. typedef void(* ptr_to_func) (int);
  2. //typedef聲明表示ptr_to_func是這種函數(shù)指針結(jié)構(gòu)的別名,可以用ptr_to_func在其他地方替換它代表的函數(shù)指針結(jié)構(gòu)
  3. /*表示ptr_to_func是一個函數(shù)指針,函數(shù)接收int參數(shù),返回void*/
  4. ptr_to_func signal (int, ptr_to_func);
  5. /* signal是一個函數(shù),它接受兩個參數(shù),一個是int,一個是ptr_to_func,返回ptr_to_func*/

typedef int x[10]和#define x int[10]的區(qū)別
1.typedef定義的類型別名不能擴展

  1. #define peach int
  2. unsigned peach i; /* 沒問題 */
  3. typedef int banana;
  4. unsigned banana i; /* 錯誤!非法 */

2.在連續(xù)的變量聲明中,用typedef定義的類型能夠保證聲明中所有的變量均為同一種類型,用#define定義的類型無法保證。

  1. #define int_ptr int *
  2. int ptr chalk, cheese;
  3. // 經(jīng)過宏擴展變成int * chalk, cheese;這使得chalk和cheese是不同的類型
  4. typedef char * char_ptr;
  5. char_ptr Bentley, Rolls_Royce;
  6. // Bentley和Rolls_Royce的類型依然相同

typedef聲明引入了my_type這個“別名”作為“struct my_tag {int i;}”的簡寫形式,但同時引入了結(jié)構(gòu)標(biāo)簽my_tag,

  1. typedef struct my_tag {int i;} my_type;
  2.     struct my_tag variable_1;
  3. my_type variable_2;

但是如果用同一個標(biāo)志符表示標(biāo)簽和別名,那么以后使用這個標(biāo)志符號時前面就不必使用關(guān)鍵字struct,但是這個向灌輸了一種
完全錯誤的思維方式;為了使代碼清晰,結(jié)構(gòu)標(biāo)簽和typedef別名不能用相同的標(biāo)志符。

  1. typedef struct fruit {int weight,price;} fruit; /* 定義了結(jié)構(gòu)標(biāo)簽fruit和別名fruit */
  2.         struct veg{int weight, price;} veg; /* 定義了結(jié)構(gòu)標(biāo)簽veg和變量veg */
  3. struct fruit mandarin; /* 使用結(jié)構(gòu)標(biāo)簽fruit */
  4.        fruit mandarin; /* 使用結(jié)構(gòu)類型fruit */
  5. struct veg potato; /* 可以接受 */
  6. veg potato; /* 不能接受 */

鏈接:函數(shù)指針與指針函數(shù)的解釋有助于理解復(fù)雜的函數(shù)聲明,參考---https://www.cnblogs.com/code1527/p/3249027.html 

始終從內(nèi)往外找標(biāo)志符;
優(yōu)先級:被括號括起來的聲明 > () 和 []  >  *;

int *pfun(int, int)

pfun左邊*右邊(),()優(yōu)先級高于*,pfun(int,int)結(jié)合,pfun是函數(shù),然后從pfun函數(shù)名往左找返回值,找到*,* pfun函數(shù)返回指針,然后繼續(xù)int * pfun(), 指針指向int類型;總結(jié)下來就是,pfun是一個函數(shù),返回指向int類型的指針

int (*pfun)(int, int);

找到標(biāo)識符pfun,pfun被()和*一起括起來后優(yōu)先級高,(*pfun)是一個指針,往右讀結(jié)合()后,(*pfun)(int, int)說明指針指向函數(shù),然后從(*pfun)往左找函數(shù)的返回類型int;總結(jié)是pfun是一個指針,指向返回int類型的函數(shù).

int *(*x[10])(void);

找到標(biāo)志符x,x左側(cè)*右側(cè)[],[]優(yōu)先級高于*,x結(jié)合[]得到x是一個數(shù)組x[],然后與*括起來得到(*x[10])表示數(shù)組元素是指針,(*x[10])左側(cè)*右側(cè)(),()優(yōu)先級高于*,則(*x[10])(void)表示數(shù)組的元素是指針,而指針指向參數(shù)為void的函數(shù),最后從(*x[10])向左找函數(shù)的函數(shù)類型*,*(*x[10])(void)表示指針指向的函數(shù)返回類型是指針,然后繼續(xù)往左int *(*x[10])(void)指針指向一個int類型;總結(jié)是x是數(shù)組,數(shù)組的元素是指針,指針指向函數(shù),函數(shù)返回指向int類型的指針。

int (*ff(int))(int *, int);

找到標(biāo)志符ff,左側(cè)*右側(cè)(),()優(yōu)先級高于*,ff()表示ff函數(shù),然后被括號括起來(*ff(int)),ff的左側(cè)是*表示函數(shù)的返回類型是指針,然后結(jié)合右側(cè)括號后得到(*ff(int))(int*,int),ff函數(shù)返回的指針指向一個參數(shù)為(int *,int)的函數(shù),然后從(*ff(int))往左找函數(shù)的返回類型是int;總結(jié)是,ff是一個函數(shù),函數(shù)返回一個指針,指針指向參數(shù)為(int*, int)返回int類型的函數(shù);這樣的用法晦澀難懂,一般會這樣用typedef int(*PF)(int*, int); PF是一個指針,用typedef聲明后,則PF成為函數(shù)指針“類型”,可以當(dāng)做函數(shù)的返回類型;
PF ff(int),用PF聲明函數(shù),這樣來替代int (*ff(int))(int*,int)。
 

Chapter 4

為什么指針和數(shù)組很多情況下會被錯誤認為是可以互換的?答案是對數(shù)組的引用總是可以寫成對指針的引用;
extern char a[] 與extern char a[100]等價,都表示a是一個數(shù)組,也就是一個內(nèi)存地址。
編譯器并不需要知道數(shù)組總共有多長,因為它只產(chǎn)生偏離起始地址的偏移地址;而指針的訪問必須明確知道指針的地址。
數(shù)組和指針的訪問不同在于,編譯器符號表存儲的是數(shù)組的首地址,再加上偏移量就可以訪問數(shù)組元素,
對指針而言存儲的是指針符號,需要先訪問符號p的地址,取符號p的內(nèi)容,最后取p的內(nèi)容指向的字符;
定義成指針,以數(shù)組訪問時

  1. char *p = 'abcdefg'; ... p[3]
  2. char a[] = 'abcdefg' ... a[3]

兩者都可以取得字符“d”,指針p獲得字符d的過程
1、取得符號表p的地址,提取存儲于此處的指針,
2、把下標(biāo)所表示的偏移量與指針的值相加,產(chǎn)生一個地址,
3、訪問上面這個地址,取得字符;

指針與數(shù)組的區(qū)別

定義指針時,編譯器并不為指針?biāo)赶虻膶ο蠓峙淇臻g,它只是分配指針本身的空間,除非為用字符串常量初始化指針;
char *p = 'bread', 在ANSI C中,初始化指針?biāo)鶆?chuàng)建的字符串常量被定義為只讀;初始化數(shù)組所創(chuàng)建的字符串常量是可以被修改的char a[] = 'strawberry'; strncpy(a, 'black', 5);
 

Chapter 5

編譯系統(tǒng):預(yù)處理器->編譯器->匯編器->鏈接器;
預(yù)處理 (C Preprocessor):預(yù)處理以字符#開頭的命令,修改原始的C程序,得到.i作為文件擴展名;
編譯階段: 將文本文件.i翻譯成文本文件.s,它包含一個匯編語言程序,匯編語言為不同的編譯器提供了通用的輸出語言;
如下所示hello.s

  1. main:
  2.     subq $8, %rsp
  3.     movl $.lCO, %edi
  4.     call puts
  5.     movl $0, %eax
  6.     addq $8, %rsp
  7.     ret

匯編階段:匯編器將.s翻譯成機器語言指令,并打包成可重定位目標(biāo)程序(relocatable object program),并將結(jié)果保存在hello.o中,hello.o文件是一個二進制文件,用文本編輯器打開是一堆亂碼;
鏈接階段 :hello程序調(diào)用了printf函數(shù),它是每個C編譯器都提供的標(biāo)注C庫的一個函數(shù)。printf函數(shù)存在于一個名為printf.o的單獨的預(yù)編譯好了的目標(biāo)文件中,而這個文件必須以某種方式合并到hello.o中。鏈接就是把.o文件合并在一起得到hello文件,它是一個可執(zhí)行目標(biāo)文件,可以被加載到內(nèi)存,處理器讀取并解釋儲存在內(nèi)存中的指令。

GNU環(huán)境包括EMACS編輯器,GCC編譯器,GDB調(diào)試器,處理二進制文件的工具以及其他一些部件,但內(nèi)核是Linux的;gcc編譯器支持多種語言,C,C++,Fortran,Java,Pascal,Object-C

系統(tǒng)的硬件組成:總線,通常總線被設(shè)計成傳送定長的字節(jié)塊,也就是字(word),字中的字節(jié)數(shù)(字長)是一個基本的系統(tǒng)參數(shù),32位機器字長是四個字節(jié),64位機器是8個字節(jié);I/O設(shè)備:鼠標(biāo),鍵盤,顯示器,磁盤都有一個控制器或適配器與總線相連;主存是一個線性的字節(jié)數(shù)組,每個字節(jié)都有都有其唯一的地址,一般來說程序的每條指令都由不同數(shù)量的字節(jié)構(gòu)成;處理器:解釋存儲在主存中指令的引擎,處理器的核心是一個大小為一個字的存儲設(shè)備(寄存器),成為程序計數(shù)器(PC),任何時候,PC都指向主存中的某條機器語言指令(含有該條指令的地址)。處理器從系統(tǒng)上電到斷電一直不斷的執(zhí)行程序計數(shù)器指向的指令,再更新程序計數(shù)器,使其指令下一條指令。處理器看上去是按照一個非常簡單的指令執(zhí)行模型來操作的,這個模型是指令集架構(gòu)決定的。定的。

高速緩存存儲器,緩解了處理器寄存器和內(nèi)存之間的讀取速度差異,作為暫時的集結(jié)區(qū)域,存放處理器近期可能會需要的信息。j進程是操作系統(tǒng)對一個正在運行的程序的一種抽象,在一個系統(tǒng)上可以同時運行多個進程;而并發(fā)進行則是說一個進程的指令和另一個進程的指令是交錯執(zhí)行的。操作系統(tǒng)實現(xiàn)處理器在進程間切換的機制稱為上下文切換。進程到另一個進程是由操作系統(tǒng)內(nèi)核管理的,內(nèi)核是操作系統(tǒng)代碼常駐主存的部分。一個進程實際上由多個稱為線程的執(zhí)行單元組成,每個線程都運行在進程的上下文中,并共享同樣的代碼和全局數(shù)據(jù)。

虛擬內(nèi)存為每個進程提供了完整的虛擬地址空間,讓進程認為它獨占內(nèi)存。進程的虛擬地址空間,

程序被編譯成二進制文件=代碼段(text)+ 數(shù)據(jù)段(data)+ Bss段 ;

在運行時程序在內(nèi)存中分布 = 代碼段(text)+ 初始化數(shù)據(jù)段(data) + 未初始化數(shù)據(jù)段(Bss) + 堆(heap)+ 棧(stack)
 

Bss中存儲未初始化的數(shù)據(jù),所以data與bss的區(qū)別在于bss只存儲變量需要占據(jù)的內(nèi)存大小但并不分配內(nèi)存,要為data分配內(nèi)存

以上內(nèi)容可參考博客:

https://blog.csdn.net/YuZhiHui_No1/article/details/38458711

https://blog.csdn.net/gatieme/article/details/43567433

https://blog.csdn.net/K346K346/article/details/45592329

https://blog.csdn.net/love_gaohz/article/details/41310597

https://blog.csdn.net/zhangskd/article/details/6956638

https://blog.csdn.net/acs713/article/details/9055193  ( 字符數(shù)組分配在棧中分配內(nèi)存;而字符串分配在文字常量區(qū)(數(shù)據(jù)區(qū)));

指令集并行:現(xiàn)代處理器可以同時執(zhí)行多條指令的屬性成為指令集并行。

當(dāng)鏈接一個程序時,需要使用的每個庫函數(shù)的一份拷貝被加入到可執(zhí)行文件中。今年來更新的動態(tài)鏈接逐漸被采用。動態(tài)鏈接庫提供一個龐大的函數(shù)庫集合,程序?qū)⒃谶\行時尋找需要的函數(shù),而不是把函數(shù)庫的二進制代碼作為自身可執(zhí)行文件的一部分。
如果函數(shù)庫的一份拷貝作為可執(zhí)行文件的物理組成部分,稱之為靜態(tài)鏈接;如果可執(zhí)行文件名只是包含了文件名,讓載入器在運行時能夠?qū)ふ页绦蛩枰暮瘮?shù)庫,稱之為動態(tài)鏈接。執(zhí)行可執(zhí)行文件的三個階段是鏈接-編輯(link-editing)、載入(loading)和運行時鏈接(runtime linking);
 靜態(tài)鏈接和動態(tài)鏈接
靜態(tài)鏈接的模塊在鏈接編輯并載入等待運行,動態(tài)鏈接的模塊被鏈接編輯后載入,在運行時進行鏈接以便運行。程序執(zhí)行時,在main()函數(shù)被調(diào)用之前,運行時載入把共享的數(shù)據(jù)對象載入到進程的地址空間。外部函數(shù)被真正調(diào)用之前,運行時載入器并不解析它們。因此即使鏈接了函數(shù)庫,如果并沒有實際調(diào)用,也不會帶來額外開銷。 
動態(tài)鏈接的主要目的是把程序和它使用的特定的函數(shù)庫版本分離開來,取而代之的是,約定由系統(tǒng)向程序提供一個接口,并保持穩(wěn)定,不隨時間和操作系統(tǒng)的后續(xù)版本發(fā)生變化。由于它是介于應(yīng)用程序和函數(shù)庫二進制可執(zhí)行文件所提供的服務(wù)之間的接口,所以稱為應(yīng)用程序的二進制接口(Application Binary Interface,ABI)。動態(tài)鏈接必須保證4個特定函數(shù)庫:libc(C 運行時函數(shù)庫)libsys(其他系統(tǒng)函數(shù)),libX(X windowing)和libnsl(網(wǎng)絡(luò)服務(wù))。
動態(tài)鏈接的可執(zhí)行文件體積可以很小,雖然運行速度稍微慢一些,但可以更有效的利用磁盤空間和虛擬內(nèi)存,因為函數(shù)庫只有在需要時才被映射到進程中,鏈接器通過把庫文件名或路徑名植入到可執(zhí)行文件來做到這一點,而且鏈接編輯階段的時間也會縮短(因為鏈接器的有些工作被推遲到載入時)。所有動態(tài)鏈接到某個特定函數(shù)庫的可執(zhí)行文件在運行時共享該函數(shù)庫的一個單獨拷貝,這就提供了更高的I/O交換和交換空間利用率。
 動態(tài)鏈接是一種just-in-time鏈接,意味著程序運行時必須能夠找到它們所需要的函數(shù)庫。鏈接器通過把庫文件名或路徑名植入到可執(zhí)行文件中來做到這一點。這意味著,函數(shù)庫的路徑不能隨意移動。當(dāng)在一臺機器人編譯完成后,拿到另一臺機器上運行時可能會出現(xiàn)這種情況。
靜態(tài)鏈接擴展名“.a”(archive),動態(tài)鏈接擴展名“.so”(shared object,表示每一個鏈接到該函數(shù)庫的程序都共享它的一份拷貝);創(chuàng)建靜態(tài)或動態(tài)的函數(shù)庫,只需簡單地編譯一些不包含main函數(shù)的代碼,并把編譯所產(chǎn)生的.o文件用正確的實用工具處理,如果是靜態(tài)庫,實用“ar”,如果是動態(tài)庫,使用“l(fā)d”.
 
參考博客:Unix編譯C語言程序----https://blog.csdn.net/ZR_Lang/article/details/17080335

Chapter 6

運行時系統(tǒng);
a.out是assembler output“匯編程序輸出”的縮寫形式(由于歷史原因),但實際上是鏈接器輸出。
UNIX系統(tǒng),ELF,Extensible Linker Format 可擴展鏈接器格式,現(xiàn)在指Executable and Linking
 Format,可執(zhí)行文件和鏈接格式;section是ELF文件中最小組織單位,一個段(Segment)一般包含幾個
section,段表示一個二進制文件相關(guān)的內(nèi)容塊。
size a.out會得到a.out文件中三個段(文本段,數(shù)據(jù)段和bss段),bss段是Block Started by Symbol(由符號
開始的塊),bss字段只保存沒有值的變量;
a.out以segment(段)的形式組織,因為段可以方便的映射到運行時鏈接器直接載入載入的對象中。載入器只是取
文件中每個段的映像,并放入到內(nèi)存中。
堆棧段在程序執(zhí)行時用于保存臨時數(shù)據(jù),局部變量,傳遞到函數(shù)中的參數(shù)等,還有malloc分配的動態(tài)內(nèi)存。
過程活動記錄,跟蹤調(diào)用鏈;
 

Chapter 7

虛擬地址,頁
虛擬內(nèi)存鏈接:
https://juejin.im/post/59f8691b51882534af254317;
https://www.cnblogs.com/zl1991/p/8193398.html
進程只能操作位于物理內(nèi)存的頁面。當(dāng)進程引用一個不存在的物理內(nèi)存頁面時,MMU(內(nèi)存管理單元)產(chǎn)生頁錯誤,內(nèi)核會判斷該引用是否有效。如果無效,內(nèi)核向進程發(fā)出一個'segmentation violation(段違規(guī))'的信號。如果有效內(nèi)核從磁盤取回該頁,換入到內(nèi)存中。一旦頁面進入內(nèi)存,進程便被解鎖,可以重新運行---進程本身并不知道它曾經(jīng)因為頁面換入事件等待了一會
 
訪問速度排序,CPU寄存器>Cache存儲器>內(nèi)存>磁盤;虛擬內(nèi)存以“頁”的形式組織。頁就是在操作系統(tǒng)在磁盤和內(nèi)存之間移動的對象,一般大小幾K字節(jié)。內(nèi)存的映像在磁盤和物理內(nèi)存之間來回移動,稱為page in(移入內(nèi)存)或page out(移到磁盤)。虛擬地址按頁劃分,頁映射到物理內(nèi)存或虛擬內(nèi)存上,頁被加載入內(nèi)存時MMU(內(nèi)存管理單元)會將虛地址轉(zhuǎn)換成物理內(nèi)存地址。
野指針可能會訪問沒有建立與物理內(nèi)存映射的虛擬內(nèi)存。內(nèi)核空間根據(jù)獨立且唯一的頁表init_mm.pgd進行映射,而用戶空間的頁表則每個進程維護自己的一份。
 
Cache包含一個地址的列表以及它們的內(nèi)容,隨著CPU不斷引用新的內(nèi)存地址,Cache的地址列表也一直處于變化中。所有對于內(nèi)存的讀取和寫入都會經(jīng)過Cache。當(dāng)處理器要引用的地址在Cache中,它可以立刻從Cache中獲取。否則,Cache就向內(nèi)存?zhèn)鬟f這個請求,于是就要較緩慢的訪問內(nèi)存操作。內(nèi)存讀取的數(shù)據(jù)以行為單位,在讀取的同時也轉(zhuǎn)入到Cache中。在UNIX中,內(nèi)存就是磁盤inode的Cache.在關(guān)閉電源前需要把Cache的內(nèi)容刷新到磁盤,文件系統(tǒng)就有可能損壞。
Cache的訪問單位是'行'(line),每行有兩部分組成,數(shù)據(jù)部分以及用于指定它所代表的地址的標(biāo)簽;數(shù)據(jù)部分被稱為“塊”(block),塊保存來回移動于Cache行和內(nèi)存之間的字節(jié)數(shù)據(jù),典型塊大小為32字節(jié);一個Cache行的內(nèi)容代表特定的內(nèi)存塊,如果處理器試圖訪問屬于該塊對應(yīng)范圍地址的內(nèi)存,Cache會立刻做出反應(yīng)不需要向內(nèi)存?zhèn)鬟f請求,這樣自然就會塊很多。一個Cache(一般為64K到1M之間)由許多行組成。為了提高速度,Cache的位置離CPU很近,而且內(nèi)存系統(tǒng)和總線經(jīng)過高度優(yōu)化,盡可能地提高大小等于Cache塊的數(shù)據(jù)塊的移動速度。
 
就像堆棧段能夠根據(jù)需要內(nèi)存上的堆區(qū)域用于動態(tài)內(nèi)存的分配。堆內(nèi)存的回收不必與它所分配的順序一致,所以無序的malloc/free最終會產(chǎn)生堆碎片內(nèi)存損壞,釋放或改寫仍在使用的內(nèi)存;內(nèi)存泄露:未釋放不再使用的內(nèi)存。
 
函數(shù)參數(shù)中,數(shù)組是作為指針來傳遞的,所以函數(shù)參數(shù)中數(shù)組和指針形式是可以互換的,譬如main函數(shù)中的char **argv和char *argv[]使用快速的左移運算代替緩慢的加法預(yù)算;“表達式中的數(shù)組名”就是指針;C語言把數(shù)組的下標(biāo)改寫成指針偏移量的根本原因是指針和偏移量是底層硬件所使用的基本模型;”作為函數(shù)參數(shù)的數(shù)組名“等同于指針;ANCI C標(biāo)準(zhǔn)規(guī)定作為”類型的數(shù)組“的形參的聲明應(yīng)該調(diào)整為”類型的指針“。在函數(shù)形參定義這個特殊情況下,編譯器必須把數(shù)組形式的形參改寫成指向該數(shù)組第一個元素的指針形式。編譯器只向函數(shù)傳遞數(shù)組的地址,而不是整個數(shù)組的拷貝。為什么C語言把數(shù)組形參當(dāng)做指針?在C語言中,所有非數(shù)組形參的數(shù)據(jù)均以傳值形式(對實參做一份拷貝并傳給調(diào)用的函數(shù),函數(shù)不能修改作為實參的實際變量的值,而只能修改傳遞給它的那份拷貝)調(diào)用。如果拷貝整個數(shù)組,內(nèi)存空間和時間上開銷都非常大。
 

Chapter 9

  1. void fun1(int *ptr)
  2. {
  3. ptr[1] = 3;
  4. *ptr = 3;
  5. ptr = array2;
  6. }
  7. void fun2(int arr[])
  8. {
  9. arr[1] = 3;
  10. *arr = 3;
  11. arr = array2; //雖然arr申明為數(shù)組,但是作為函數(shù)形參會被編譯器轉(zhuǎn)成指向數(shù)組第一個元素的指針
  12. }
  13. int array[100], array2[100];
  14. main()
  15. {
  16. array[1] = 3;
  17. *array = 3;
  18. array = array2;//編譯失敗,因為array這里就是數(shù)組,不等同于指針,數(shù)組名作為左值是不能被修改
  19. }
  20. 鏈接:https://www.cnblogs.com/Howe-Young/p/4160289.html
  21. int (*array)[20] //指針,指向20個元素的int數(shù)組
  22. int *array[20] //指針數(shù)組,每一個元素指向int類型
  23. int (*paf())[20] {
  24. int (*pear)[20]; /* 聲明一個指向包含20個int元素的數(shù)組的指針 */
  25. pear = calloc(20, sizeof(int));
  26. if(!pear) longjmp(error, 1);
  27. return pear;
  28. }
  29. //調(diào)用函數(shù)
  30. int (*result)[20];
  31. result = paf(); //調(diào)用函數(shù)
  32. (*result)[3] = 12; //訪問數(shù)組
  33. //定義結(jié)構(gòu)包含數(shù)組,讓函數(shù)返回結(jié)構(gòu)
  34. struct a_tag {
  35. int array[20];
  36. } x,y;
  37. struct a_tag my_function() {
  38. ……
  39. return y;
  40. }
  41. //函數(shù)不可以返回指向局部變量的指針,但可以返回局部變量,確切的說,函數(shù)不能返回指向調(diào)用棧的指針,
  42. //但是可以返回指向調(diào)用堆的指針;將局部變量聲明為static,變量就保存在數(shù)據(jù)段而不是調(diào)用棧中,該變量
  43. //聲明周期與程序一樣長,函數(shù)退出時,變量的值依然存在

Chapter 11

面向?qū)ο笞兂傻奶攸c是集成和動態(tài)綁定。C++通過類的派生支持集成,通過虛擬函數(shù)支持動態(tài)綁定。虛擬函數(shù)提供了一種封裝類體系實現(xiàn)細節(jié)的方法。

繼承與嵌套不同。嵌套常用于實現(xiàn)容器類(數(shù)據(jù)結(jié)構(gòu)的類,如鏈表,散列表,隊列等)。C++的模板也用于實現(xiàn)容器類。

C++使用<<操作符(輸入)和>>操作符(輸出)來替代C語言中的putchar()和getchar()等函數(shù)。

<<和>>操作符在C語言中用作左移位和右移位操作符,在C++中被重載用于C++的I/O,編譯器查看操作數(shù)的類型來決定移位還是I/O代碼。

<<和>>操作符可被定義用于任何類型,不需要像C語言一樣用字符串格式化限定符%d,并且可以連續(xù)輸出

例如,cout << 'the value is' << i << endl;

但仍然可以使用C++中的stdio.h函數(shù);

函數(shù)重載總是在編譯時進行解析,編譯器查看操作數(shù)的類型并決定使用哪種定義,因此重載函數(shù)原型必須不同,運算符重載也是函數(shù)重載。

多態(tài)-----運行時綁定,也成為后期綁定,virtual 成員函數(shù)聲明多態(tài)函數(shù);C++通過覆蓋支持(override)機制來實現(xiàn)多態(tài);當(dāng)使用類繼承時就要用到這種機制;virtual 函數(shù)的原型必須相同,由運行時系統(tǒng)決定調(diào)用哪個函數(shù),基類中用virtual聲明,virtual關(guān)鍵字不可以省略。

虛擬函數(shù)表;

異常(exception):通過發(fā)生錯誤時把自動切換到程序中用于處理錯誤的那部分代碼,從而簡化錯誤處理

模板(template):支持參數(shù)化類型。同類型/對象的關(guān)系一樣,函數(shù)/模板的關(guān)系也可以看作是為算法提供一種“甜點刀具”的方法,一旦確定了基本的算法就可以用于不同的類型。例如,template<class T> T min(T a, T b) { return  (a<b) ? a : b },允許對min函數(shù)和變量a,b賦予任意的類型(該類型必須能接收<操作符)

內(nèi)聯(lián)(inline):規(guī)定某個函數(shù)在行內(nèi)以指令流的形式展開(就像宏一樣),而不是產(chǎn)生一個函數(shù)調(diào)用。

new 和 delelte操作符,用于取代malloc和free函數(shù)。new和delete用來更方便一些(如能夠自動完成sizeof的計算工作,并會自動調(diào)用合適的構(gòu)造函數(shù)和析構(gòu)函數(shù)),new能夠真正地創(chuàng)建一個對象,而malloc只是分配內(nèi)存。

傳引用調(diào)用(call by reference,相當(dāng)于傳址調(diào)用):C語言只使用傳值調(diào)用

編程語言有一個特性,成為正交性(orthogonality),是指不同的特性遵循同一個基本原則的程度(也就是學(xué)會一種特性有助于學(xué)習(xí)其他的特性)。

Fortran語言(Formula translation)是第一個高級語言,提供了強大的方法來表達數(shù)學(xué)公式

C語言設(shè)計哲學(xué)
一切工作程序員自己負責(zé);
C語言的所有特性都不需要隱式的運行時支持;
程序員所做的都是對的;
程序員應(yīng)該知道自己在干什么,并保證自己的所作所為是正確。

C++對C語言的改進
C語言中,初始化一個字符數(shù)組的方式很容易產(chǎn)生錯誤,就是數(shù)組很可能沒有足夠的空間存放結(jié)尾的NULL字符。
C++對此作了一些改進,像char b[3] = 'Bob'這樣的表達式被認為是錯誤的,但是在C語言中是合法的。
類型轉(zhuǎn)換即可以是float(i)這樣看上去更順眼的形式,也可以寫成像(float)i這樣稍怪異的C語言風(fēng)格的形式。
C++允許一個常量整數(shù)來定義數(shù)組的大小,
const int size = 128; 
char a[size];
但是C語言中卻是不合法的,C語言聲明數(shù)組的大小只能用char a[128]或char a[宏定義定義的數(shù)字]
C++聲明可以穿插于語句之間。在C語言中,一個語句塊中的所有聲明必須放在所有語句的前面。C++去掉了這個限制。

在C++中存在,但在C語言中卻不存在的限制有
C++中用戶代碼不能調(diào)用main()函數(shù),但在C語言中確實允許的;
C++中要求完整的函數(shù)原型聲明,但是在C語言中卻沒那么嚴格;
C++ typedef定義的名字不能與已有的結(jié)構(gòu)標(biāo)簽沖突,但是C語言中卻是允許的(它們分別屬于不同的名字空間)。

當(dāng)void *指針賦值給另一個類型的指針時,C++規(guī)定必須進行類型轉(zhuǎn)換,但在C語言中卻無必要。

在C++和C語言中含義不一樣的特性:

C++至少增加了十幾個關(guān)鍵字。這些關(guān)鍵字在C語言中可以作為標(biāo)識符使用,但如果這樣做了,用C++編譯器編譯這些代碼就會產(chǎn)生錯誤信息。

C++,聲明可以出現(xiàn)在語句可以出現(xiàn)的任何地方。在C語言代碼中塊中,所有的聲明必須出現(xiàn)在所有語句的前面

C++中一個內(nèi)層作用域的結(jié)構(gòu)名將會隱藏外層空間相同的對象名。在C語言中則并非如此。
 

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
c專家編程
程序的機器級表示
程序編譯過程
水滴石穿C語言之指針綜合談 - C語言、C++編程技術(shù)館 - 博客園
C interview
C語言深度剖析
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服