主頁 >  其他 > C語言高級部分總結

C語言高級部分總結

2021-02-05 07:10:55 其他

來自公眾號:技術讓夢想更偉大 來源:網路,整理:李肖遙

一、記憶體大話題

1.0、記憶體就是程式的立足之地,體現記憶體重要性,

 

1.1、記憶體理解:

 

記憶體物理看是有很多個Bank(就是行列陣式的存盤芯片),每一個Bank的列就是位寬 ,每一行就是Words,則存盤單元數量=行數(words)×列數(位寬)×Bank的數量;通常也用M×W的方式來表示芯片的容量(或者說是芯片的規格/組織結構),

 

M是以位寬為單位的總容量,單位是兆 ,W代表位寬, 單位是bit,計算出來的芯片容量也是以bit為單位,但用戶可以采用除以8的方法換算為位元組(Byte),比如8M×8,這是一個8bit位寬芯片,有8M個存盤單元,總容量是64Mbit(8MB),
1.2、c語言中其實沒有bool型別:以0表示假,非0表示真,則在記憶體存盤是以int型存放的,如果想要表示真偽,可以用int/char型做替換,在c++中就有bool x=true/false;
1.3、記憶體對齊:記憶體對齊(提高訪問效率速度,編譯器一般默認是4位元組對齊)

 

1.4、char/int/short/long/float/double型:放在記憶體的長度和決議作用,(int *)0,使0地址指向一個int型,又比如0000111010101可以決議成int型也可以決議成float型,
1.5、Linux內核是面向物件的,而c語言是面向程序的,但可以用結構體內嵌指標變成面向物件,如

 

struct student{
int age; //變數
int lenth; //將相當于一個類,有變數有函式
char *name;
void (*eat)(void); //函式指標
}

 

1.6、堆疊的理解:

 

(1) 運行時自動分配&自動回收:堆疊是自動管理的,程式員不需要手工干預,方便簡單,(表現在匯編代碼,編譯時,會自動編譯成匯編碼實作函式呼叫完立即改變堆疊頂)

 

(2) 反復使用:堆疊記憶體在程式中其實就是那一塊空間,程式反復使用這一塊空間,(硬體上有個暫存器,用來存放堆疊的堆疊頂地址,堆疊是有大小的空間)

 

(3) 臟記憶體:堆疊記憶體由于反復使用,每次使用后程式不會去清理,因此分配到時保留原來的值,

 

(4) 臨時性:(函式不能回傳堆疊變數的指標,因為這個空間是臨時的)

 

(5) 堆疊會溢位:因為作業系統事先給定了堆疊的大小,如果在函式中無窮盡的分配堆疊記憶體總能用完,堆疊的操作(怎么出堆疊怎么入堆疊)是由具體硬體來干預,程式員只要明白原理就可以了,但是要給相應的堆疊暫存器賦值,當呼叫函式時,變數會自動放在堆疊中(入堆疊)當函式呼叫完后,堆疊會自動出堆疊.

 

( 6 ) 堆疊的 "發展"有四種情況,滿增堆疊,滿減堆疊,空增堆疊,空減堆疊,至于是那種要根據編譯器決定,而s5pv21 是滿減堆疊,
1.7、堆的理解:

 

(1)作業系統堆管理器管理:堆管理器是作業系統的一個模塊,堆管理記憶體分配靈活,按需分配,

 

(2)大塊記憶體:堆記憶體管理者總量很大的作業系統記憶體塊,各行程可以按需申請使用,使用完釋放,

 

(3)臟記憶體:堆記憶體也是反復使用的,而且使用者用完釋放前不會清除,因此也是臟的,

 

(4)臨時性:堆記憶體只在malloc和free之間屬于我這個行程,而可以訪問,在malloc之前和free之后都不能再訪問,否則會有不可預料的后果,

 

(5)程式手動申請&釋放:手工意思是需要寫代碼去申請malloc和釋放free,(記住:不要把申請的地址給搞丟了, 不然自己用不了,也釋放不了)

 

申請一段記憶體,可以是:

malloc(10*sizeof ( int ) );

 

原型:

void *malloc(size_t size); 

 

//指標函式 size_t是宏定義int 都是便于可移植性 ,回傳一個記憶體地址,void *可以看出,希望申請的記憶體用來存放什么就強制型別什么,

 

calloc( 10,sizeof ( int ) ); 原型:void *calloc(size_t nmemb, size_t size);// nmemb個單元,每個單元size位元組void *realloc(void *ptr, size_t size);// 改變原來申請的空間的大小的ptr是原來申請記憶體的指標,size是想要重新申請記憶體的大小使用就是*(p+1)=12 ; *(P+3)=110;

 

申請失敗回傳NULL,申請成功回傳一個地址,申請之后一定要檢驗(NULL!=p)用完一定要 free ( p ) ;釋放后不是不能用,是不應該使用了,可以給它“洗盤子‘,p=NULL;

 

其實申請的記憶體并不能真正改變大小,原理是先重新申請一段記憶體,然后把原來申請的記憶體上的內容復制到新的記憶體上,然后釋放掉原來的記憶體,回傳新的指標,

 

(6) 在申請記憶體時,malloc(0)其實也是成功的,因為系統規定少于一定數目的大小,都申請規定的大小,如在win32系統下申請少于32位元組的地址,最后申請到的空間是32位元組,在朱老師視頻中申請少于16位元組的地址,最后申請到的是16位元組,至于規定多少位元組,由具體的系統而言,
1.8、記憶體里的資料:

 

(1)代碼段:存放代碼二進制、常量(char *p="linux",則”linux“存放在代碼段,是不可更改的)

 

(2) 資料段: 存放非0全域變數、靜態區域變數(區域只屬于函式的,不是整個程式的)

 

(3) bss : 存放為0的全域變數/為0的靜態區域變數、存放未初始化全域變數/靜態區域變數

 

注意:const int a=9; 有兩種存放方式:第一種確實存放在代碼段,讓a不能修改,第二種是仍然存放在資料段中,讓編譯器來判斷,如果有改變的代碼就會報錯,至于那種,是不確定的,像單片機就屬于第一種,
1.9、《1》一個源檔案實際上是以段為單位編譯成連接成可執行檔案(a .out );這個可執行檔案總的說是分為資料段,代碼段,自定義段,資料段還可以細分成 .bbs 段,而雜段會在執行的時候拿掉,所以a.out分為雜段,資料段(存放的是非0全域變數).bbs段,代碼段,

 

《2》記憶體實際上被劃分了兩大區域,一個是系統區域,另一個是用戶區域,而每一個區域又被劃分成了幾個小區域,有堆,堆疊,代碼區,.bbs區,資料區(存放的是非0全域變數),

 

《3》對于有作業系統而言, 當我們在執行a.out可執行檔案時,執行這個檔案的那套程式會幫我們把雜段清掉,然后把相應的段加載到記憶體對應的段,對于裸機程式而言,我們是使用一套工具將a.elf的可執行程式給清掉了所有段的符號資訊,把純凈的二進制做成.bin格式的燒錄檔案,所以我們加載到記憶體的程式是連續的,也就是說代碼段和資料段、.bbs段都是連續的,當然,堆疊空間是我們自己設定的,而且在裸機中我們不能使用malloc函式,因為我們使用的只是編譯器、連接器工具沒有集成庫函式,沒有定義堆空間區,

 

《4》大總結多程式運行情況:在Linux系統中運行cdw1.out時,運行這個檔案的那套程式會幫我們把相應的段加載到記憶體對應的段,然后作業系統會把下載到記憶體的具體物理地址與每條命令(32位)的鏈接地址映射到TTB中(一段記憶體空間),當我們又運行cdw2.out時,同樣也像cdw1.out一樣加載進去,并映射到TTB表中,而且這兩個.out檔案默認都是鏈接0地址(邏輯),當cpu發出一個虛擬地址(Linux中程式邏輯地址)通過TTB查找的物理地址是不一樣的,所以對于每一個程式而言,它獨占4G的記憶體空間,看不到其他程式,

二、位操作

2.1 ~(0u)是全1;
2.2 位與& 位或 | 位取反~ 位異或^
2.3、位與、位或、位異或的特點總結:

 

位與:(任何數,其實就是1或者0)與1位與無變化,與0位與變成0
位或:(任何數,其實就是1或者0)與1位或變成1,與0位或無變化
位異或:(任何數,其實就是1或者0)與1位異或會取反,與0位異或無變化
2.4、左移位<< 與右移位>> C語言的移位要取決于資料型別,
對于無符號數,左移時右側補0(相當于邏輯移位)
對于無符號數,右移時左側補0(相當于邏輯移位)
對于有符號數,左移時右側補0(叫算術移位,相當于邏輯移位)
對于有符號數,右移時左側補符號位(如果正數就補0,負數就補1,叫算術移位)
2.5、小記:常與 1 拿來 做位運算,讓他取反、移位 得到想要的數,
2.6、直接用宏來置位、復位(最右邊為第1位),置位置1,復位置0 ;
#define SET_NTH_BIT(x, n) (x | ((1U)<<(n-1)))
#define CLEAR_NTH_BIT(x, n) (x & ~((1U)<<(n-1)))
}

三、指標—精髓

3.1 printf("%p \n"); 其中%p表示輸出一個指標,就是指標變數(其存放的那個地址),可以理解為輸出一個地址,
3.2 int* p1, p2 ; 等同于 int *p1; int p2;  int *p="Linux",其不能改變*P,因為”linux"是一個常數,
3.3 ( 代碼規范性 )在定義指標時,同時賦值為NULL,在用指標時,先判斷它是不是NULL,尤其是在malloc申請記憶體后,free(p);則一定要讓p=NULL
3.4 C/C++中對NULL的理解:{ #ifdef _cplusplus// 定義這個符號就表示當前是C++環境

 

#define NULL 0;// 在C++中NULL就是0
#else
#define NULL (void *) 0;// 在C中NULL是強制型別轉換為void *的0
#endif

 

3.5、修飾詞:const (修飾變數為常量,應該理解為不應該去變它,當作常量,而并非永遠不能改變,當然要看具體運行環境,在gcc,const 這種就可以采用指標方式修改,但是在在VC6.6++中就不可以修改):其雖然是當作常數,但是仍然存放在資料段中,用指標仍然可以改變值,

 

第一種:const int *p;
第二種:int const *p;
第三種:int * const p;
第四種:const int * const p;

 

3.6、 陣列 int a[2]; 其中a是指首元素的首地址,&a是整個陣列的收地址(陣列指標,其這個指標指向一個陣列),他們的值是一樣的,但意義不一樣,可以參照 int a; int *p=&a; 來理解,陣列和指標天生姻緣在于陣列名;

 

int a[3]; int* p=a;是可以的,但是 int *p=&a;就會報錯,盡管他們的值是一樣的,但意義不一樣,所以是不允許的,除非強制型別轉換,在訪問時是a[0],其實編譯器會把它變成*(a+0)的方式,只是用a[0]看起來更方便,封裝了一下而已,實質還是指標,
3.7、 siziof()是一個運算子,測驗所占記憶體空間,如 int a[100] ;sizeof(a)=400;

 

與strlen( )要有所區別,他是測字串實際長度的,不包括‘\0‘,如果給strlen傳的引數不是一個字串,則它會一直去找,直到 找到第一個 ‘\0’,然后再計算其長度,

 

如 char a[]="chen"; char *p=a; 則strlen(p)=4;
3.8、 當陣列作為一個形參時,其實參是一個陣列名(也可以是指標,其本質就是指標),意義是首元素的首地址,則傳過去只影響形參的第一個元素,形引陣列的地址被實引陣列地址所系結;

 

實參的大小會丟失,所以往往會傳一個int num 大小進去,
3.9、 結構體做為形參時,應盡量用指標/地址方式來傳,因為結構體變數有時會占很大,效率很低,
4.0、 int *p=&u; p存放的是變數u的地址,而&p的意思就是變數p本身的地址,
4.1、當要傳參的個數比較多時,我們可以打包成一個結構體,傳參的個數越多,其開銷就更大. 
4.2 一個函式作用其實就是輸入輸出,引數可以作為輸入,回傳可以作為輸出,但是當要回傳多個輸出時,這時候就不夠用了,所以常常回傳值用來判斷程式有沒有出錯,而引數就是當作輸入輸出的,輸入時可以加const表示它沒必要去修改,而輸出都是指標,因為要改變它的值,只能采用地址傳遞這種方式,比如:char *strcpy(char *dest,const char *src)

四、C語言復雜運算式

4.1、在運算式中,要看符號的優先級和結合性,
4.2、在理解記憶體時,記憶體0地址在最底下,至上地址逐漸增加,
4.3、int *p;是定義的一指標變數p,而int ( *p)[4];也是一個指標變數p;也可以這樣想:凡是遇到(*p)什么的判斷他是指標后,就可以說他是指標變數,包括函式指標,
4.4、一個函式 int max(int a ,int b); 則他的函式指標是 int ( *p ) (int ,int );其意思就是定義這個型別的函式指標變數p; p=max是賦值,參考是p();則相當于max()呼叫這個函式,

 

函式指標必須和原函式的型別一樣,

 

4.5 函式指標其實就是為了做結構體內嵌指標的,這樣就構成了高級語言中的類,再一個就是上述4.4中p=&max;也是可以的,它和p=max,值和意義都是一樣的,這個和陣列有所區別,陣列的a和&a的值雖然一樣,但是意義完全不一樣,int a[4];a有兩層意思,第一層是陣列名,&a表示整個陣列的地址,第二層表示首元素的首地址,
4.6 int (*p[4])(int ,int)其意思是函式指標陣列,一個4長度的陣列,里面存了4個函式指標,
* 4.7 printf在做輸出時,其機制是緩沖行來輸出,即當遇到一個\n后再列印出來,即使再多printf,沒有遇到\n,都不是一個一個列印,

 

'\r'是回車,'\n'是換行,前者使游標到行首,后者使游標下移一格,通常敲一個回車鍵,即是回車,又是換行(\r\n),Unix中每行結尾只有“<換行>,即“\n”;Windows中每行結尾是“<換行><回車>”,即“\r\n”;Mac中每行結尾是“<回車>”,scanf("");里面不要加\n符,
4.8 在一個c檔案中,有時候會多次引入一個.h檔案,所以在寫.h檔案時,要寫

 

{#ifndef _FINE_
#define _FINE_
XXXXXXXX
XXXXXXXXXXX
#endif }

 

4.9、typedef int *intType; const intType p,其意思是指標p為const;
4.9.1 對于typedef的定義:如typedef const int cdw; 可以這樣理解,typedef就是給一個型別區別名的,那么系統會自動識別該型別,如果typedef const int char 則就報錯,
4.9.2 在開發中經常會typedef int int32_t ; typedef short int16_t; 這樣做的目的是便于在不同平臺下的移植,如果當在另一個平臺下,int 是64位的,但是我的專案中都是用的int32_t;

 

所以只需要修改int32_t就可以了,我可以讓他typedef short int32_t;這樣我只更改一次,其余的都改了,做到一改全改,
** 4.9.3 int **p; int *a[4]; p=a;可以這樣理解:首先它是指標陣列,既然是陣列,則a即表示陣列名又表示首元素的首地址,a[0]是一個一重指標,而a是a[0]的地址,那么a就是一個二重指標;{ 一重指標的地址就是二重指標變數,所以有p=a; 而 int a[4][3] ,a和一維陣列的意思是一樣的,如 int a[3][6],int *p ;p=a[0];所以不能p=a,int *a[3][3],int **p;p=a[0];}
** 4.9.4、二維陣列是為了簡化編程,平面型,陣列以下標示方式訪問其實是編譯器封裝起來的,實質是指標訪問,int (*p)[5]; int a[2][5];則有 p=a; 關鍵是要把二維陣列抽象成n行n列用指標訪問方式理解:二維陣列可以看作是一個方格子的矩陣,比如a[2][5],那么就是2行5列的10個小格子,第一行可以收納起來變成一個指向一維陣列的指標,第二行也是如此;

 

這樣收納后就變成了一個新的陣列a[2],每一個格子存放的是它收納的第一個元素的地址,如a[0]存放的是第一行第一列元素的地址,“a”[1]存放的是第二行第一列的地址;

 

再與一維陣列相聯系,一維陣列名即表示陣列名又表示陣列第一個元素的地址,所以a[2][5]中的a表示“a"[2]陣列第一個元素的地址;那么再把p=a;層層推遞,(p+i)表示指向第幾行的地址,*(p+i)表示取第幾行的值(而這個值存放的是第幾行一列元素的首地址),*(p+i)+j 表示指向第幾行第幾列的地址,最后在參考這個地址,*(*(p+i)+j)就表示第幾行第幾列的值了,

 

一重指標----------->一維陣列
二重指標----------->指標陣列
陣列指標----------->二維陣列
函式指標----------->普通函式

五、陣列&字串&結構體&共用體&列舉(5.6?)

5.1、c語言中定義一個字串: char a[6]={'l','i','n','u','x','\0'}; '\0'的字符編碼為0就是NULL;也就是說記憶體中遇到0,翻譯成字符是就是'\0',或這是NULL;

 

char a[6]="linux";
char *p="linux";

 

5.2、 sizeof(a)=6是運算子,其意思是所占空間大小,包括字串后面的‘\0',strlen(a)=5是一個函式,其意思是字串的長度,strlen( p);其中p只有是字符指標變數才有意義,它的形參是陣列變數是沒有意義的,因為strlen是根據什么時候遇到 '\0',才結束測驗字串的長度,就算陣列不初始化也是有長度的,

 

char *p="linux"; sizeof(p)永遠等于4,因為p是指標變數,存的是地址,所以總結:sizeof()是拿來測陣列的大小,strlen()是拿來測驗字串的長度,
5.3、結構體用 . 或者是 ->訪問內部變數,其實質是用的指標訪問,如

 

struct student{
int a;
double b;
char c;
}s1;

 

則s1.a =12;實質就是int *p=(int *) &s1;*p=12 首先a是int 型,所以是強制型別 int * ,其次是就是算地址,然后強制型別,地址應該是int 型然后加減,不然的話,系統s1.b=12.2;實質就是double *p= (double *) ((int)&s1+4),*p=12.2; 不知道是以int 型加級訓是以float型加減,還是以char型加減,所以 應當 (int)&s1; 而且因為地址是s1.c=c;實質就是 char *p=(char *) ((int)&s1+12); *p=c; 4位元組的,所以必須是int型,
&* 5.4、對齊方式:

 

(1)猜測如果是32位系統,那么編譯器默認是4位元組對齊,64位系統,那么編譯器默認是8位元組對齊,因為32位或64位一次性訪問效率是最高的,

 

(2)

 

<1>結構體首地址對齊(編譯器自身幫我們保證,會給它分配一個對齊的地址,因為結構體自身已經對齊了,那么第一個變數也就自然對齊,所以我們才可以想象成第一個變數從0地址存放);

 

<2>結構體內部的各個變數要對齊,

 

<3>整個結構體要對齊,因為定義結構體變數s1時,再定義變數s2時,如果s1沒有對齊,就坑了s2,所以也要保證整個結構體對齊,

 

無論是按照幾位元組對齊,我們都可以聯想到記憶體實際的安排,1位元組對齊那么不管int float double 型別,在每4個格子的記憶體挨著存放,2位元組對齊,也是一樣的想法,舉一個列子,如果第一個變數是char 型,第二個變數是int型,那么0地址存放char型,1地址空著,2地址存放int型地址部分,3地址存放int型地址部分,然后上排最右4、5地址存放int型高址部分,4位元組對齊,如果第一個變數是char型,第二個變數是int型,那么0地址存放char型,1,2,3地址空著,從4地址開始存放int,最后給變數分配完記憶體空間后,必須要保證整個結構體對齊,下一個結構體的存放起始地址是n位元組對齊整數倍,如是4位元組對齊,那么最后short算成4位元組 以保證整個結構體對齊,

 

整個結構體對齊,如2位元組對齊(2的整數倍),只要是0、2、4地址就行了,如果是4位元組對齊(4的整數倍),就必須是0、4地址,8位元組對齊(8的整數倍)

 

(3)猜測4位元組/8位元組其實是針對int型/double型的,比如0地址是char型,那么4位元組對齊,int型、float型就必須從4地址開始存放,那么8位元組對齊,int型就必須從4地址存放,double型就必須從8地址開始存放.小于幾位元組對齊的那些,如char型和short型只要能按照規則存放就行了,

 

(4)對齊命令:<1>需要#prgama pack(n)開頭,以#pragma pack()結尾,定義一個區間,這個區間內的對齊引數就是n,(不建議使用)

 

如:s1占5個位元組,s2占8位元組(默認)

 

#pragma pack(1)
struct stu1
{

 

(結構體本身以及變數) 對齊規則:2位元組對齊(2的整數倍),只要是0、2、4地址就行了,4位元組對齊(4的整數倍),就必須是0、4地址,
8位元組對齊(8的整數倍),就必須是0、8、16

 

char c;
int a;
//short d;
}s1;


struct stu2
{


char c;
int a;
//short d;
}s2;

 

<2>gcc推薦的對齊指令__attribute__((packed)) __attribute__((aligned(n))),在VC中就不行,沒有定義這個命令

 

(1)__attribute__((packed))使用時直接放在要進行記憶體對齊的型別定義的后面,然后它起作用的范圍只有加了這個東西的這一個型別,packed的作用就是取消對齊訪問,

 

(2)__attribute__((aligned(n)))使用時直接放在要進行記憶體對齊的型別定義的后面,然后它起作用的范圍只有加了這個東西的這一個型別,它的作用是讓整個結構體變數整體進行n位元組對齊(注意是結構體變數整體n位元組對齊,而不是結構體內各元素也要n位元組對齊,內部元素按照默認對齊方式)

 

例子:

 

struct mystruct11
{// 1位元組對齊4位元組對齊
int a;// 44
char b;// 12(1+1)
short c;// 22
}__attribute__((packed));


typedef struct mystruct111
{// 1位元組對齊4位元組對齊2位元組對齊
int a;// 44 4
char b;// 12(1+1)2
short c;// 22 2
short d;// 2 4(2+2)2
}__attribute__((aligned(1024))) My111;

 

5.5、offsetof宏:#define offsetof( TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)

 

(1)offsetof宏的作用是:用宏來計算結構體中某個元素和結構體首地址的偏移量(其實質是通過編譯器來幫我們計算),

 

(2)offsetof宏的原理:我們虛擬一個type型別結構體變數,然后用type.member的方式來訪問那個member元素,繼而得到member相對于整個變數首地址的偏移量,

 

(3)學習思路:第一步先學會用offsetof宏,第二步再去理解這個宏的實作原理,

 

(TYPE *)0 這是一個強制型別轉換,把0地址強制型別轉換成一個指標,這個指標指向一個TYPE型別的結構體變數,(實際上這個結構體變數可能不存在,但是只要我不去解參考這個指標就不會出錯),

 

((TYPE *)0)->MEMBER(TYPE *)0是一個TYPE型別結構體變數的指標,通過指標指標來訪問這個結構體變數的member元素,然后對這個元素取地址,又因為改地址是從0地址開始算的,所以這個地址就是相對起始地址的偏移量,
5.6 container_of宏:#define container_of(ptr, type, member) ({\
const typeof(((type *)0)->member) * __mptr = (ptr);\
(type *)((char *)__mptr - offsetof(type, member)); }) 兩條陳述句;,然后用{ } ,\表示提示編譯器本行因為螢屏不夠,鏈接下一行,用#(也就是宏定義)時,如果本行不夠要用 \ 提示編譯器接著是下一行的,必須要用 \ ,猜測因為宏定義一行就算結束了,
(1)作用:知道一個結構體中某個元素的指標,反推這個結構體變數的指標,有了container_of宏,我們可以從一個元素的指標得到整個結構體變數的指標,繼而得到結構體中其他元素的指標,

 

(2)typeof關鍵字的作用是:typepef(a)時由變數a得到a的型別,typeof就是由變數名得到變數資料型別的,

 

(3)這個宏的作業原理:先用typeof得到member元素的型別定義成一個指標,然后用這個指標減去該元素相對于整個結構體變數的偏移量(偏移量用offsetof宏得到的),減去之后得到的就是整個結構體變數的首地址了,

 

再把這個地址強制型別轉換為type *即可,
5.7 p是一個地址,(int)p+6 和(char *)+6;效果是一樣的,第一種是將地址p當作int型加減,第二種是將地址p做為char *指標,他每次加減都是一位元組一位元組相加減的,如果是 (int *)P+6,那么他每次加減都是按照4位元組一跳,就相當于加了+4*6;
5.8 小端模式:變數的高地址存放在高地址,低地址存放在低地址;通信模式也要分大小端,先發送/接受的是高地址還是低地址,大端模式:變數的高地址存放在低地址,低地址存放在高地址;

 

測驗:有用共用體 union 和指標方式來測驗,基本思路是讓 int a=1; 看低地址 char 是0還是1 ;變數都是從地址開始存放,只是變數的高地址和低地址先存放誰不確定,

 

不能用位與來測,因為存放和讀取都是按照某一個方式來的,結果永遠都是一樣的,int a=1; char b=(char)a;這種方式不可以測驗,因為不管大小端,它都以變數a的低地址部分賦給b;

 

union stu{
int a; int ce( )
{
int a=1;
int b=*((char *)&a);
return b;
}
char b;
}
int ce( )
{
union stu s;
s.a=1;
return s.b;
}

 

5.9、列舉型別(int型):這樣寫默認從第一個常量開始是0,1,2,3,4.........也可以自己賦值,但是每一個值是不一樣的,否則邏輯上出錯,

enum week{sunday, sunday=1,moday, moday=5,tuseday, 然后其他常量以此遞增,wenzeday,friday,saterday,}today; today=sunday;* // 錯誤1,列舉型別重名,編譯時報錯:error: conflicting types for ‘DAY’typedef enum workday{MON, // MON = 1;TUE,WEN,THU,FRI,}DAY;typedef enum weekend{SAT,SUN,}DAY;*// /錯誤2,列舉成員重名,編譯時報錯:redeclaration of enumerator ‘MON’typedef enum workday{MON, // MON = 1;TUE,WEN,THU,FRI,}workday;typedef enum weekend{MON,SAT,SUN,}weekend;}

六、C語言宏定義與預處理、函式和函式庫(看博客strcyp原函式)

6.1、原始碼.c->(預處理)->預處理過的 .i 檔案->(編譯)->匯編檔案.S->(匯編)->目標檔案.o->(鏈接)->elf可執行程式預處理用前處理器,編譯用編譯器,匯編用匯編器,鏈接用聯結器,這幾個工具再加上其他一些額外的會用到的可用工具,合起來叫編譯工具鏈,gcc就是一個編譯工具鏈,
<1>預處理的意義(1)編譯器本身的主要目的是編譯源代碼,將C的源代碼轉化成.S的匯編代碼,編譯器聚焦核心功能后,就剝離出了一些非核心的功能到前處理器去了,

 

(1)前處理器幫編譯器做一些編譯前的雜事,如:(1)#include(#include <>和#include ""的區別)

 

(2)注釋

 

(3)#if #elif #endif#ifdef

 

(4)宏定義

 

備注:gcc中只預處理不編譯的方法 -o生成可執行檔案名 -c只編譯不鏈接 -E 只預處理不編譯 -I ( 是大i,不是L )編譯時從某個路徑下尋找頭檔案 . /當前

 

(1)gcc編譯時可以給一些引數來做一些設定,譬如gcc xx.c -o xx可以指定可執行程式的名稱;譬如gcc xx.c -c -o xx.o可以指定只編譯不連接,也可以生成.o的目標檔案,

 

(2)gcc -E xx.c -o xx.i可以實作只預處理不編譯,一般情況下沒必要只預處理不編譯,但有時候這種技巧可以用來幫助我們研究預處理程序,幫助debug程式,

 

(3)聯結器:鏈接的時候是把目標檔案(二進制)通過有序的排列組合起來,如 star.s main.c led.c 這三個源檔案,分別被編譯成三個目標檔案 ,每個目標檔案有很多函式集合,鏈接的時候會根據運行思路把這些雜亂的函式給排列組合起來,不是把目標檔案簡單的排列組合,

 

(4)當生成可執行程式之后,這個可執行程式里面有很多符號資訊,有個符號表,里面的符號與一個地址相對應,如 函式名max對應一個地址,雖然這個程式有符號資訊,但是為什么還是可以執行呢?因為如windows的exe程式,有專門的一套程式來執行這個.exe 檔案,就好比壓縮檔案,就有一套 “好壓”的軟體,然后去壓縮(執行).rar .zip的檔案,而這套程式就把這些符號資訊給過濾掉,然后得到純凈的二進制代碼,最后把他們加載到記憶體中去,

 

(5) debug版本就是有符號資訊,而Release版本就是純凈版本的,可用strip工具:strip是把可執行程式中的符號資訊給拿掉,以節省空間,(Debug版本和Release版本)objcopy:由可執行程式生成可燒錄的鏡像bin檔案,

 

6.2、預處理:

 

<1>頭檔案有”“是本目錄去找,找不到就去庫頭檔案找,和< > 只到庫頭檔案去找,庫頭檔案可以自己制作,用 -I ( 是大i,不是L )引數去尋找路徑,

頭檔案在預處理時,會把檔案的內容原封不動的賦值到 c 檔案里面,

 

<2>注釋:在預處理時,把注釋全部拿掉,注意:#define zf 1 再判斷 #undef zf 2 時,也是通過的,其意思是有沒有定義過zf.

 

<3>條件編譯:當作一個配置開關 #define NUM 表示定義了NUM,則執行下一條陳述句,且NUM用空格替代,而且預處理會刪掉條件編譯,留下正確的執行陳述句,

 

<4>宏定義:#define cdw 1 在預處理階段,會替代那些宏,可以多重替代宏;也可以表示多個陳述句,如 #define cdw printf("cdw\n") ; printf("zf\n"); cdw;這條陳述句會直接展開
還有帶參宏,#define max(a,b) ((a)+(b)) 注意的是帶參宏一定要( ) 不然有時候會引起錯誤,每一個”形參“都應該要();
#define year (365*24*60*60*60*60 ) 安理說是可以的,但是year是int型的已經超過了范圍,所以要把它搞成無符號長整形,
#define year (365*24*60*60*60*60ul ) 這樣才是正確的
宏定義的變數是不占記憶體空間的,直接替換減少開銷,但是變數替換是不進行型別檢查;
函式的變數要占用空間、要壓堆疊等操作,就需要很大的開銷,但是呼叫函式時,編譯器會檢查函式變數的型別是否相同,
行內函式集合普通函式、宏定義的兩個優勢,它直接就地展開,直接替換,減少開銷,同時編譯器也會檢查變數的型別,但是函式體積要小,不然效率反而很低,至于
原因暫時不詳,
6.3、行內函式:對函式就地展開,像宏定義一樣,這樣減少開銷,同時也檢查變數的型別,但是必須函式的內部體積小才用這種方式,以達到更好的效率,體積大的函式就作為普通函式,
行內函式通過在函式定義前加inline關鍵字實作,
* 6.4、條件編譯的應用:做一個除錯開關,#define DEBUG #undef DEBUG 是注銷 DEBUG 宏
#ifdef DEBUG
#define debug(x) printf(x)
#else
#define debug(x)
#endif
6.5、函式:

 

(1)整個程式分成多個源檔案,一個檔案分成多個函式,一個函式分成多個陳述句,這就是整個程式的組織形式,這樣組織的好處在于:分化問題、便于撰寫程式、便于分工,

 

(2)函式的出現是人(程式員和架構師)的需要,而不是機器(編譯器、CPU)的需要,

 

(3)函式的目的就是實作模塊化編程,說白了就是為了提供程式的可移植性,

 

<1>函式書寫的一般原則:

 

第一:遵循一定格式,函式的回傳型別、函式名(男女廁所)、引數串列(太多用結構體)等,

 

第二:一個函式只做一件事:函式不能太長也不宜太短(一個螢屏的大小),原則是一個函式只做一件事情,

 

第三:傳參不宜過多:在ARM體系下,傳參不宜超過4個,如果傳參確實需要多則考構體打包考慮,

 

第四:盡量少碰全域變數:函式最好用傳參回傳值來和外部交換資料,不要用全域變數,

 

<2> 之所以函式能被呼叫,根本實質是在編譯時,檢查到了該函式的宣告,不是因為函式定義了(當然也要定義才行,只是不是本質),
6.6、遞回函式:自己呼叫自己的函式,常用舉例:階乘 int jiecheng( int n) 斐波那契數例:f(n)=f(n-1)+f(n-2) n>2的正整數

 

{ int he(int n)

 

注意:if(n<1) if(3==n||4==n)

 

堆疊溢位:遞回函式會不停的耗費堆疊空間 { {
所以要注意遞回不要太多 printf("error\n"); return 1;
收斂性:必須 要有一個終止遞回的條件 } }

 

else if(n>1) else if(n>4)
{ {
return n*jiecheng(n-1); return he(n-1) +he(n-2)
} }
else
{
return 1;
}

 

6.7、函式庫:<1>靜態鏈接庫其實就是商業公司將自己的函式庫源代碼經過只編譯不連接形成.o的目標檔案,然后用ar工具將.o檔案歸檔成.a的歸檔檔案(.a的歸檔檔案又叫靜態鏈接庫檔案),

 

商業公司通過發布.a庫檔案和.h頭檔案來提供靜態庫給客戶使用;客戶拿到.a和.h檔案后,通過.h頭檔案得知庫中的庫函式的原型,然后在自己的.c檔案中直接呼叫這些庫檔案,在連接的時候聯結器會去.a檔案中拿出被呼叫的那個函式的編譯后的.o二進制代碼段鏈接進去形成最終的可執行程式,

 

<2>元件本身不將庫函式的代碼段鏈接入可執行程式,只是做個
標記,然后當應用程式在記憶體中執行時,運行時環境發現它呼叫了一個動態庫中的庫函式時,會去加載這個動態庫到記憶體中,然后以后不管有多少個應用程式去呼叫這個庫中的函式都會跳轉到第一次加載的地方去執行(不會重復加載),

 

也就是在運行時,會把庫函式代碼放入記憶體中,然后多個程式要用到庫函式時,就從這段記憶體去找,而靜態鏈接對于多程式就是重復使用庫函式,比較占記憶體,

 

(1) gcc中編譯鏈接程式默認是使用動態庫的,要想靜態鏈接需要顯式用-static來強制靜態鏈接,

 

(2) 庫函式的使用需要注意3點:第一,包含相應的頭檔案;第二,呼叫庫函式時注意函式原型;第三,有些庫函式鏈接時需要額外用-lxxx來指定鏈接;第四,如果是動態庫,要注意-L指定動態庫的地址,
6.8、常見的兩個庫函式:<1>C庫中字串處理函式包含在string.h中,這個檔案在ubuntu系統中在/usr/include中字串函式 如:memcpy(記憶體字串復制,直接復制到目標空間)確定src和dst不會overlap重復,則使用memcpy效率高memmove(記憶體字串復制,先復制到一個記憶體空間,然后再復制到目標空間)確定會overlap或者不確定但是有可能overlap,則使用memove比較保險
memset strncmp
memcmp strdup
???? memchr strndup
strcpy strchr
strncpy strstr
strcat strtok
strncat ,,,
strcmp

 

<2> 數學函式:math.h 需要加 -lm 就是告訴聯結器到libm中去查找用到的函式,

 

C聯結器的作業特點:因為庫函式有很多,聯結器去庫函式目錄搜索的時間比較久,為了提升速度想了一個折中的方案:聯結器只是默認的尋找幾個最常用的庫,如果是一些不常用的庫中的函式被呼叫,需要程式員在鏈接時明確給出要擴展查找的庫的名字,

 

鏈接時可以用-lxxx來指示聯結器去到libxxx.so中去查找這個函式,
6.9、自制靜態鏈接庫:

 

(1)第一步:自己制作靜態鏈接庫,首先使用gcc -c只編譯不連接,生成.o檔案;然后使用ar工具進行打包成.a歸檔檔案庫名不能隨便亂起,一般是lib+庫名稱,后綴名是.a表示是一個歸檔檔案
注意:制作出來了靜態庫之后,發布時需要發布.a檔案和.h檔案,

 

(2)第二步:使用靜態鏈接庫,把.a和.h都放在我參考的檔案夾下,然后在.c檔案中包含庫的.h,然后直接使用庫函式,

 

備注:

 

<1>.a 檔案,前綴一定要加lib ,如 libzf.a ; 鏈接屬性 -l(小L),表示庫名,屬性-L表示庫的路徑,所以:gcc cdw.c -o cdw -lzf -L ./include -I(大i) ./include

 

<2> 頭檔案“ ”表示外部自定義,如果沒加路徑屬性,默認當前路徑找,如果在其他檔案夾下,必須用 -I(大i) 路徑,用<>表示的頭檔案一種是在編譯器下的庫檔案找,第二種是自己定義的庫檔案找,但是要定義其路徑,

 

<3> 在makefile檔案中用到gcc/arm-gcc 那么在shell中就用相應的編譯器 gcc/arm-gcc .

 

<4> nm ./include/libmax.a 查看max庫的資訊,有哪些 .o 檔案 .o檔案有哪些函式,

 

舉例:makefile: arm-gcc aston.c -o aston.o -c
arm-ar -rc libaston.a aston.o
6.9.1、自制元件:

 

<1>元件的后綴名是.so(對應windows系統中的dll),靜態庫的擴展名是.a .

 

<2>第一步:創建一個元件,gcc aston.c -o aston.o -c -fPIC (-fPIC表示設定位置無關碼)
gcc -o libaston.so aston.o -shared (-shared表示使用共享庫)
注意:做庫的人給用庫的人發布庫時,發布libxxx.so和xxx.h即可,

 

第二步:使用自己創建的共享庫,gcc cdw.c -o cdw -lmax.so -L ./元件 -I ./元件

 

第三步:上述編譯成功了,但是在 ./cdw 執行時會報錯,原因是采用動態鏈接,在可執行檔案只是做了一個標記,標記是使用了哪個函式庫的哪個函式,

 

并沒有將庫函式加載到源檔案中,所以可執行檔案很小,在執行時,需要立即從系統里面找到使用到的函式庫,然后加載到記憶體中,在linux系統中
默認是從 /usr/bin 中尋找,(不確定:如果使用shell中運行)會先執行環境變數的路徑然后再查找 /usr/bin;所以我們可以用兩種辦法解決運行的問題

 

第四步:將動態庫 libmax.so 復制到 /usr/lib 下面,但是如果以后所有的庫都這樣放的話,會越來越臃腫,導致運行速度變慢(一個一個查找);或者是新添加一個環境變數

 

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/share/include (將庫 libmax.so 復制到這個路徑下面)這樣就可以運行了,

 

<3>使用 ldd 命令判斷一個可執行檔案是否能運行成功;ldd cdw
linux-gate.so.1 => (0xb77a8000)
libmax.so => not found //發現 not found意思就是沒有找到對應的函式庫
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e2000)
/lib/ld-linux.so.2 (0xb77a9000)

七、存盤類&作用域&生命周期&鏈接屬性

7.1、概念詞:存盤類(堆疊、堆、資料區、.bss段、.text段)
作用域(代碼塊作用范圍,也就是變數作用的范圍)
生命周期(變數的誕生和死亡)
鏈接屬性(外鏈接屬性、內鏈接屬性、無連接屬性)
7.2、Linux下的記憶體映射(分配情況、組織情況):見圖記憶體映射,其中有關行程的空間,如行程控制塊、頁表等都是在內核里面的,檔案區是映射外部檔案的,如打開記事本,那么這個檔案臨時存放在檔案區域,(見參考資料)

 

問題:虛擬地址技術?解決:后期在Linux應用/網路編程會講,

 

OS下和裸機下C程式加載執行的差異?解決:在arm裸機第十六部分有介紹,
7.3、存盤類關鍵字:

 

<1> auto 自動的(一個用法:修飾區域變數,在定義變數時可以省略) 【外鏈接:與第二個c檔案鏈接】【內鏈接:只與本c檔案鏈接】【無連接:就是無鏈接】

 

<2> static 靜態的(有兩個用法,第一個是修飾區域變數,意思是當作全域變數,存放在資料區,作用域只是定義的那個函式范圍,生命周期和整個程式一樣,屬于無連接

 

第二個是修改全域變數/函式,意思是這個全域變數/函式只在當前c檔案有效,其他c檔案是不能使用它的,屬于內鏈接,普通全域變數屬于外連接)
<3>register 暫存器(一個用法,修飾變數,作用是讓編譯器把這個變數放在暫存器中,當這個變數頻繁的被使用時,用這個方法可以提高效率,但有時候不一定就放在暫存器,因為暫存器是有限的,沒有剩余的暫存器了)

 

<4>extern (一個用法,修飾全域變數,表示該檔案要使用的這個變數,在另外一個c檔案中已經定義了,其一個宣告的作用,不能初始化)

 

<5>volatile (一個用法,修飾變數,表示對這個變數的陳述句不要去優化)

 

(1) volatile的字面意思:可變的、易變的,C語言中volatile用來修飾一個變數,表示這個變數可以被編譯器之外的東西改變,編譯器之內的意思是變數的值的改變是代碼的作用,編譯器之外的改變就是這個改變不是代碼造成的,或者不是當前代碼造成的,編譯器在編譯當前代碼時無法預知,譬如在中斷處理程式isr中更改了這個變數的值,譬如多執行緒中在別的執行緒更改了這個變數的值,譬如硬體自動更改了這個變數的值(一般這個變數是存在暫存器,或許當其他行程要用到這個暫存器時,就把這個暫存器的變數給改變了,同時也就改變了這個變數)

 

(2) 以上說的三種情況(中斷isr中參考的變數,多執行緒中共用的變數,硬體會更改的變數)都是編譯器在編譯時無法預知的更改,此時應用使用volatile告訴編譯器這個變數屬于這種(可變的、易變的)情況,編譯器在遇到volatile修飾的變數時就不會對改變數的訪問進行優化,就不會出現錯誤,

 

(3) 編譯器的優化在一般情況下非常好,可以幫助提升程式效率,但是在特殊情況(volatile)下,變數會被編譯器想象之外的力量所改變,此時如果編譯器沒有意識到而去優化則就會造成優化錯誤,優化錯誤就會帶來執行時錯誤,

 

而且這種錯誤很難被發現,

 

(4) volatile是程式員意識到需要volatile然后在定義變數時加上volatile,如果你遇到了應該加volatile的情況而沒有加程式可能會被錯誤的優化,如果在不應該加volatile而加了的情況程式不會出錯只是會降低效率,

 

所以我們對于volatile的態度應該是:正確區分,該加的時候加不該加的時候不加,如果不能確定該不該加為了保險起見就加上,

 

舉例子1:int a=3 ,b,c;
b=a;
c=b;

 

那么編譯器會優化成 c=b=a=3; 如果此時遇到上述三種情況,突然改變了a的值,那么,對于沒有優化前是對的,但是對于優化后,那么c仍然是3,就會出錯,

 

所以當我們程式員知道這個變數會發生改變時,就應該加 volatile,提示編譯器不要幫我們做優化,
舉例子2:

 

int square(volatile int *ptr)
   {
   return *ptr * *ptr;
   }

 

這段代碼的有個惡作劇,這段代碼的目的是用來返指標*ptr指向值的平方,但是,由于*ptr指向一個volatile型引數,編譯器將產生類似下面的代碼:

 

   int square(volatile int *ptr)
  {
   int a,b;
   a = *ptr;
   b = *ptr;
   return a * b;
   }

 

由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的,結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:

 

   long square(volatile int *ptr)
   {
    int a;
   a = *ptr;
   return a * a;
   }

 

<6> restrict (1)c99中才支持的,所以很多延續c89的編譯器是不支持restrict關鍵字,gcc支持的,

 

(2)restrict 作用是讓編譯器對這個變數做一些優化,讓它提高效率,下面的網站有列子,

 

(3)restrict只用來修飾指標,不能修飾普通變數,它表示只能該指標才能修改它的內容,如用memcpy時,兩個記憶體存在重疊的現象,

 

(4)https://blog.chinaunix.net/uid-22197900-id-359209.html (這個網站里面有詳細的例子)

 

(5)memcpy和memmove的區別 void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n);這樣它可以優化成memmove原理的方式(當存在重疊時,先復制到一段記憶體空間,然后再把它復制到目的空間)
7.4、作用域:

 

(1)全域變數起名字一般是 g_a;

 

(2)名字加前綴表示
7.5、總結:<1>區域變數地址由運行時在堆疊上分配得到,多次執行時地址不一定相同,函式不能回傳該類變數的地址(指標)作為回傳值,

 

<2>為什么要分為資料段和.bbs段?因為當加載到記憶體重定位時,如果這些資料(包括0)一個一個的復制,會降低效率,為0的變數,直接清記憶體就可以了,這樣提高效率,

 

<3>在頭檔案宣告全域變數時, extern int a; 宣告函式就是 void int max(int a, int b);

 

<4>寫程式盡量避免使用全域變數,尤其是非static型別的全域變數,能確定不會被其他檔案參考的全域變數一定要static修飾,(因為全域變數占記憶體的時間是最長的,要看你的變數是不是需要這么長的時間,這樣可以節約記憶體空)

八、一些雜散但值得討論的問題

8.1、作業系統的理解:

 

<1>它是一個管理階級者,管理所有資源,負責調配優化等操作,這樣想象,就像裸機一樣的話,要實作LED閃爍的行程、串口傳輸的行程、蜂鳴器等這些,他們都要搶占一些資源,這個時候沒有作業系統,就亂成一鍋粥,當有了OS的時候,它就專門負責資源的調配,讓各個任務都能很好的實施,起一個決策者的作用,

 

<2>如果我們要做一個產品,軟體系統到底應該是裸機還是基于作業系統呢?本質上取決于產品本身的復雜度,只有極簡單的功能、使用極簡單的CPU(譬如單片機)的產品才會選擇用裸機開發;一般的復雜性產品都會選擇基于作業系統來開發,

 

<3>作業系統負責管理和資源調配,應用程式負責具體的直接勞動,他們之間的介面就是API函式,當應用程式需要使用系統資源(譬如記憶體、譬如CPU、譬如硬體操作)時就通過API向作業系統發出申請,然后作業系統回應申請幫助應用程式執行功能,
8.2、C庫函式和API的關系:

 

<1>從內核的角度看,需要考慮提供哪些有用的API函式,并不需要關注它們如何被使用,故編程時用API函式會感覺到不好用,沒有做優化,系統只負責做出一個可以用的API,沒有考慮到用戶使用的方便性,所以c庫函式對API做了一些優化,讓用戶使用庫函式更容易達到我們想要的目的,

 

<2>庫函式實質還是用的API,或者呼叫了一個API,也或者呼叫了更多的API,只不過是做了更多的優化,比如 庫函式fopen ,而API是open.

 

<3>有的庫函式沒有用到API,比如strcpy函式(復制字串)和atoi函式(轉換ASCII為整數),因為它們并不需要向內核請求任何服務,
8.3、不同平臺(windows、linux、裸機)下庫函式的差異

 

(1)不同作業系統API是不同的,但是都能完成所有的任務,只是完成一個任務所呼叫的API不同,

 

(2)庫函式在不同作業系統下也不同,但是相似性要更高一些,這是人為的,因為人下意識想要屏蔽不同作業系統的差異,因此在封裝API成庫函式的時候,盡量使用了同一套介面,所以封裝出來的庫函式挺像的,

 

但是還是有差異,所以在一個作業系統上寫的應用程式不可能直接在另一個作業系統上面編譯運行,于是乎就有個可移植性出來了,

 

(3)跨作業系統可移植平臺,譬如QT、譬如Java語言,
8.4、

 

<1>main()函式的寫法:int main(int argc,char **argv) ; int main(int argc ,char *argv[ ] ); 這兩種寫法是一樣的,二重指標等同于指標陣列,

 

<2>不管是主函式還是功能函式,它都應該有一個回傳值,而主函式的回傳值是給呼叫的那個人的/main函式從某種角度來講代表了我當前這個程式,或者說代表了整個程式,main函式的開始意味著整個程式開始執行,
main函式的結束回傳意味著整個程式的結束,誰執行了這個程式,誰就呼叫了main,誰執行了程式?或者說程式有哪幾種被呼叫執行的方法?一個程式當然會運行,那么就是呼叫了main( ).

 

<3>inux下一個新程式執行的本質

 

(1)表面來看,linux中在命令列中去./xx執行一個可執行程式

 

(2)我們還可以通過shell腳本來呼叫執行一個程式

 

(3)我們還可以在程式中去呼叫執行一個程式(fork exec)

 

總結:我們有多種方法都可以執行一個程式,但是本質上是相同的,linux中一個新程式的執行本質上是一個行程的創建、加載、運行、消亡,linux中執行一個程式其實就是創建一個新行程然后把這個程式丟進這個行程中去執行直到結束,

 

新行程是被誰開啟?在linux中行程都是被它的父行程fork出來的,

 

分析:命令列本身就是一個行程,在命令列底下去./xx執行一個程式,其實這個新程式是作為命令列行程的一個字行程去執行的,

 

總之一句話:一個程式被它的父行程所呼叫,

 

結論:main函式回傳給呼叫這個函式的父行程,父行程要這個回傳值干嘛?父行程呼叫子行程來執行一個任務,然后字行程執行完后通過main函式的回傳值回傳給父行程一個答復,這個答復一般是表示子行程的任務執行結果完成了還是錯誤了,

 

(0表示執行成功,負數表示失敗,正規的要求回傳失敗的原因,回傳-1表示什么,回傳-2又表示什么,然后父行程好做相應的處理)

 

(4) main函式的回傳值應當是int 型,父行程要求是int型的,如果寫成 float 型,則回傳就為0,這樣是要出錯的,
8.5 用shell腳本來看main()的回傳值,如:#!/bin/sh 其文本格式為 .sh
./a.out
echo $?
8.6、argc、argv與main函式的傳參:當我們的父行程不需要傳參時,就用 int main(void);當我們需要傳參時,就應該是 int main(int argv ,char *argc[ ]);它默認本身就是一個引數,占了argv[0]這個位置,它里面存的是 ./a.out (這個相應變化)

 

如:./a.out boy girl ;則 argv=3; argc[0]="./a.out"; argc[1]="boy"; argc[2]="girl" ; printf("%s\n",argc[0]);

 

解釋:argv表示傳了多少個引數,argc實質是存的一個指標,也就是一個地址,只是沒有一個被系結的變數名而已,這個地址指向一個字串,一般字串都和指標相關,所以可以稱之為字串陣列,每一個都存了一個字串,

 

在程式內部如果要使用argc,那么一定要先檢驗argv,先檢驗個數,然后使用,
8.7、void型別的本質:即使空型又是未知型別,看具體情況,比如一個函式void表示不回傳, void *malloc(20);就是未知型別,

 

(1)編程語言分2種:強型別語言和弱型別語言,強型別語言中所有的變數都有自己固定的型別,這個型別有固定的記憶體占用,有固定的決議方法;弱型別語言中沒有型別的概念,所有變數全都是一個型別(一般都是字串的),程式在用的時候再根據需要來處理變數,就如:makefile、html語言,

 

(2)C語言就是典型的強型別語言,C語言中所有的變數都有明確的型別,因為C語言中的一個變數都要對應記憶體中的一段記憶體,編譯器需要這個變數的型別來確定這個變數占用記憶體的位元組數和這一段記憶體的決議方法,

 

(3)void型別的正確的含義是:不知道型別,不確定型別,還沒確定型別、未知型別,但是將來一定有型別,

 

(4)void *a;(編譯器可以通過)定義了一個void型別的變數,含義就是說a是一個指標,而且a肯定有確定的型別,只是目前我還不知道a的型別,還不確定,所以標記為void,

 

void “修飾”的是指標,因為指標就是記憶體地址,它不知道指向的另一個變數是哪一種型別,而變數一定是確定的,void a;就是錯誤的,
8.9、C語言中的NULL

 

NULL在C/C++中的標準定義

 

(1)NULL不是C語言關鍵字,本質上是一個宏定義,其保護指標的作用,不要讓他亂開槍,

 

(2)NULL的標準定義:

 

#ifdef _cplusplus // 條件編譯c++環境
#define NULL 0
#else
#define NULL (void *)0 // 這里對應C語言的情況
#endif

 

解釋:C++的編譯環境中,編譯器預先定義了一個宏_cplusplus,程式中可以用條件編譯來判斷當前的編譯環境是C++的還是C的,

 

NULL的本質決議:NULL的本質是0,但是這個0不是當一個數字決議,而是當一個記憶體地址來決議的,這個0其實是0x00000000,代表記憶體的0地址,(void *)0這個整體運算式表示一個指標,這個指標變數本身占4位元組,地址在哪里取決于指標變數本身,但是這個指標變數的值是0,也就是說這個指標變數指向0地址(實際是0地址開始的一段記憶體),如 char *p=NULL; 因為0地址本身就不是我們來訪問的,所以 *p時是不可訪問的,在程式運行的邏輯上就不會出錯,

 

正規寫:

 

int *p = NULL;// 定義p時立即初始化為NULL
p = xx;
if (NULL != p)
{
*p // 在確認p不等于NULL的情況下才去解參考p
}

 

(1)'\0'是一個轉義字符,他對應的ASCII編碼值是0,記憶體值是0,一個char空間,

 

(2)'0'是一個字符,他對應的ASCII編碼值是48,記憶體值是int型48,一個char空間,

 

(3)0是一個數字,沒有ASCll編碼, 記憶體值是int型0,一個int空間,

 

(4)NULL是一個運算式,是強制型別轉換為void *型別的0,記憶體值是0(記憶體地址),一個int空間,
8.9.1、運算中的臨時匿名變數

 

<1>“小動作”:高級語言在運算中允許我們大跨度的運算,意思就是低級語言中需要好幾步才能完成的一個運算,在高級語言中只要一步即可完成,譬如C語言中一個變數i要加1,在C中只需要i++即可,看起來只有一句代碼,但實際上翻譯到匯編階段需要3步才能完成:第1步從記憶體中讀取i到暫存器,

 

第2步對暫存器中的i進行加1,第3步將加1后的i寫回記憶體中的i,

 

<2> float a=12.3; int b=(int)a; (int )a 就是匿名變數;先找一個記憶體空間,里面存(int)a;然后把這個值賦值給b;最后匿名值銷毀,

 

float a; int b=10; a=b/3; 左邊是3.00000;右邊是3;其中有個匿名變數,先找一個記憶體空間,里面存 b/3; 然后把它再轉換成float型,再賦值個a;最后匿名值銷毀,
8.9.2 分析DEBUG宏
學習級:

 

#define DEBUG #undef DEBUG 是注銷 DEBUG 宏
#ifdef DEBUG
#define debug(x) printf(x)
#else
#define debug(x)
#endif

 

應用級:

 

#ifdef DEBUG
#define DBG(...) fprintf(stderr, " DBG(%s, %s( ), %d): ", __FILE__, __FUNCTION__, __LINE__); fprintf(stderr, __VA_ARGS__)
#else
#define DBG(...)
#endif

 

解 釋:<1>...表示變參,提示編譯器不要對引數個數斤斤計較,不要報錯;其實完全可以把 ...換成 cdw 也是可以的,只是非要裝一下而已,

 

<2> _FILE_ 和 _FUNCTION_和 _LINE_ 都是c庫函式的宏定義,分別表示要輸出的這句話屬于哪個檔案名、屬于哪個函式名、在第幾行,

 

<3> 在 fprintf(stderr,"cdw");其中stderr是c庫函式中宏定義了的,這是VC6.0找到的 #define stderr (&_iob[2]) ;也就是說stderr是一個被宏定義了的指標,它是標準錯誤輸出流物件(stderr),輸出到螢屏上,

 

fprintf是C/C++中的一個格式化寫—庫函式,位于頭檔案中,其作用是格式化輸出到一個流/檔案中;(重點是流/檔案)

 

printf()函式是格式化輸出函式, 一般用于向標準輸出設備按規定格式輸出(重點是標準輸出設備,有時候輸出的不一定顯示在螢屏上,只是編譯器規定顯示到螢屏上而已,)

 

總結:也就是說printf()其實不是輸出螢屏上的,只是這個標準輸出設備中,編譯器規定顯示到螢屏上而已,而真正輸出到螢屏是fprintf(stderr,"cdw");其中stderr就是輸出到螢屏上的流,它也可以 fprintf( FILE *stream, const char *format,...),這個就是輸出到檔案流中的,

 

比如:一般情況下,你這兩個陳述句運行的結果是相同的,沒有區別,只有一下情況才有區別:運行你的程式的時候,命令列上把輸出結果進行的轉向,比如使用下面的命令把你的程式a.c運行的結果轉向到記事本檔案a.txt:a.exe > a.txt

 

在這樣的情況,如果使用printf輸出錯誤資訊,會保存到a.txt檔案里面,如果使用fprintf輸出錯誤,會顯示在螢屏上,

 

<4>上面中的__VA_ARGS__也是一個宏定義,表示預處理時實際的引數,
如:DBG("tiaoshi.\n");

 

則允許的效果是 DBG(debug.c, main( ), 14): tiaoshi.
內核級:

 

#ifdef DEBUG_S3C_MEM#define DEBUG(fmt, args...)printk(fmt, ##args)#else#define DEBUG(fmt, args...)do {} while (0)#endif

九、鏈表&狀態機與多執行緒(9.9.1?具體鏈表實作留到驅動模塊講解)

9.1、鏈表是一個一個的節點,每一個節點分為兩部分,一部分是資料區(可以由多個型別的資料),另一部分是指向下一個節點的指標;結構體定義里面的變數并沒有生成,是不占空間的,相當于宣告的作用,
9.2、鏈表的資料存放在記憶體的那個空間呢?(堆疊,不靈活,不能用date資料段)所以只能用堆記憶體,申請一個節點的大小并檢測NULL, 要使用它,就得清理它,因為上一個行程用了這段記憶體,存的是臟資料,
然后對這個節點記憶體賦值,鏈接起來.
9.3、當要改變頭節點是,也就是要給head=p賦值時,必須傳 head地址即 形參(struct student *head);這樣才能真正改變,不然傳一個 (struct student head)只是單純的賦值,
9.4、在scanf("%d",&(s->age)) 一定要注意,studeny *s; s->age訪問的是一個變數,而不要理解成地址,所以要加&,scanf要注意&;
9.5、細節:<1>在 .h檔案中宣告一個函式要用分號,而且是英文符號.用無頭節點的方式,需要修改頭指標位置,所以比較復雜,

 

<2> 定義一個node *head=NULL,想要改變head值通過函式傳參是不行的,因為head是一個地址,傳參過去,只是賦值給另一個指標而已,只能修改它指向的資料,而本身(地址)是不能修改的,所以要先回傳修改好的地址,然后再head=node_add(head)

 

<3>定義、用指標都應該想到NULL,如 node *head=NULL; node *new=(node *)mallo(sizeof(node));if(NULL!=new){ }

 

<4>在結構體想定義一個字串時不要用 char *name; 應該要用char name[10];如果使用第一種的話,編譯通過,執行錯誤,因為為name賦值時就要放在代碼段中,而代碼段已確定了,所以報段錯誤,
9.6、頭節點、頭指標、第一個節點:頭節點是一個節點,頭節點的下一個指向第一個節點,頭節點的資料一般存的是鏈表長度等資訊,也可以是空,頭指標指向頭節點,鏈表可以沒有頭節點,但不能沒有頭指標,

 

頭節點可以想成陣列的0位置,其余節點當作從1開始,所以有頭節點的長度可以定義為就是含有真實資料節點的個數,
9.7、洗掉一個節點應該做的事:如果這個節點的資料不重要,一定要記住free()掉,你邏輯上洗掉,其實仍然存在記憶體中的,頭節點的好處就是函式回傳值int可以幫助我們一些資訊,而沒有頭節點有時必須回傳head;
9.8、單鏈表之逆序:見代碼,
9.9、單鏈表的優點和缺點:<優點>單鏈表是對陣列的一個擴展,解決了陣列的大小比較死板不容易擴展的問題,使用堆記憶體來存盤資料,將資料分散到各個節點之間,其各個節點在記憶體中可以不相連,節點之間通過指標進行單向鏈接,鏈表中的各個節點記憶體不相連,有利于利用碎片化的記憶體,

 

<缺點>單鏈表各個節點之間只由一個指標單向鏈接,這樣實作有一些局限性,局限性主要體現在單鏈表只能經由指標單向移動(一旦指標移動過某個節點就無法再回來,如果要再次操作這個節點除非從頭指標開始再次遍歷一次),因此單鏈表的某些操作就比較麻煩(演算法比較有局限),

 

回憶之前單鏈表的所有操作(插入、洗掉節點、 遍歷、從單鏈表中取某個節點的數·····),因為單鏈表的單向移動性導致了不少麻煩,

 

總結:單鏈表的單向移動性導致我們在操作單鏈表時,當前節點只能向后移動不能向前移動,因此不自由,不利于解決更復雜的演算法,

 

9.9.1、 內核鏈表的思想是:<1>先做一個純鏈表,沒有資料區,只有節點的鏈接方法,然后要做一個鏈表出來,直接用純鏈表然后稍加修改就可以了,

 

<2>內核中__的方法不要輕易使用,是給內核用的,否則容易出錯,用戶應該使用沒有__的方法;如:__list_add() ; list_add();

 

<3>內核默認是頭指標+頭節點的思路,

 

<4>其實質就是操作里面內嵌 純鏈表這個變數,再利用controf宏來訪問結構體的資料,詳情見驅動,
9.9.2、狀態機:

 

<1>概念:其實就是有多種狀態切換,如電腦的休眠、關機、睡眠,

 

<2>型別:(1)Moore型狀態機特點是:輸出只與當前狀態有關(與輸入信號無關),相對簡單,考慮狀態機的下一個狀態時只需要考慮它的當前狀態就行了,

 

(2)Mealy型狀態機的特點是:輸出不只和當前狀態有關,還與輸入信號有關,狀態機接收到一個輸入信號需要跳轉到下一個狀態時,狀態機綜合考慮2個條件(當前狀態、輸入值)后才決定跳轉到哪個狀態,

 

<3>理解:要時時刻刻檢查當前狀態,用回圈+switch(狀態);然后根據輸入信號,進行更多的處理,轉換到其他狀態,

十、增補知識

10.1、一個位元組可以表示8位字符,字符真的有256種,128~255表示西歐字符,是不常見,詳情見檔案,字符相加的時候,會自動轉成 int型加,
10.2、在C中,默認的基礎資料型別均為signed,現在我們以char為例,說明(signed) char與unsigned char之間的區別,

 

首先在記憶體中,char與unsigned char沒有什么不同,都是一個位元組,唯一的區別是,char的最高位為符號位,因此char能表示-127~127,unsigned char沒有符號位,因此能表示0~255,這個好理解,8個bit,最多256種情況,因此無論如何都能表示256個數字,
10.3、為什么在鏈接時需要一個鏈接地址?因為資料是要放在一個模擬地址記憶體空間的,它要把這個資料先加載到暫存器,才能給cpu使用,那么暫存器怎么知道是哪個記憶體地址位置呢,是因為在編譯時,編譯出像 ldr r0 0x12345678 ,而這個0x12345678就是記憶體地址,再編譯出像 ldr r1,[r0] ,這樣就可以拿到0x12345678記憶體位置的資料了
10.4、printf 變參?
10.5、arm-2009q3.tar.bz2 這套編譯器自帶了函式庫,比如有strcmp , malloc ,printf 等,但是有些庫函式我們卻不能用他們,比如printf,因為這個函式默認是同過螢屏輸出的,而我們常用uart除錯,感覺malloc也不能用,因為我們不知道記憶體哪一塊做了堆記憶體,只有系統才知道,
10.6、清bss段:編譯器可能已經幫我們做了,只是在重定位那節,因為要重定位那部分記憶體空間并沒有清0 ,所以要手動編程清bss段,

 

C語言是每個想要學習編程的小伙伴首要學習的語言~如果你也希望成為一個好的程式員,了解C語言更多知識,點擊下方【了解更多】,接受牛人大牛們的指導,與志同道合的小伙伴一起快樂學習,共同進步~

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/256622.html

標籤:其他

上一篇:十年程式員總結:學習編程不同于學習編程語言,帶你快速搞懂它!

下一篇:最簡GIF決議代碼gif_jumper,用于stb_image的小改進

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more