C語言深度解剖----重點部分筆記
第一章:關鍵字
auto:所有變數在編譯器預設預設情況下都是auto的
register:請求編譯器儘可能的將變數存在CPU內部暫存器而不是通過記憶體定址訪問以提高效率。儘可能是因為一個CPU的暫存器也就那麼幾個或幾十個,如果定義了很多register變數是不可能全部放入的。
用&符號是去記憶體中取址,取不到放在編譯器中的地址。CPU不直接和記憶體打交道而是通過暫存器去進行交流。因此&符號不能獲得register的值。
static(除錯版):
#include<iostream> using namespace std; static int j; void fun1(void) { static int i = 0; i++; } void fun2(void) { j = 0; j++; } int main() { for (int k = 0; k < 10; k++) { fun1(); fun2(); } system("pause"); } //結果: i=1,2,3,4,5,6,7,8,9,10 j=1,1,1,1,1,1,1,1,1,1
其實主要是考察除錯的問題,fun1(),fun2()設斷點然後F5,再F10,F11的問題。
static(輸出版):
#include<iostream> using namespace std; static int j; int* fun1(void) { static int i = 0; int *a = &i;//這樣做可以獲得i的地址,也可以*i獲得i的值 i++; return a; } int* fun2(void) { j = 0; int *a = &j; j++; return a; } int main() { for (int k = 0; k < 10; k++) { fun1(); fun2(); cout << *b << endl; cout << *c << endl; } system("pause"); }
sizeof://是關鍵字不是函式
int i=0;
sizeof(int)==sizeof(i)==sizeof i==4
double a[100] = {0.0};
double *p=a;
sizeof(a);//值為800
sizeof(p);//值為4
sizeof(a[0])==sizeof(*a)//值為8
sizeof(&a)==sizeof(&a[0])//值為4,有取址符號的都為4,因為地址的sizeof為4
長迴圈放在內層可以節省效率,減少CPU切換迴圈層的次數。
符號表是一種用於語言翻譯器的資料結構,在符號表中每個識別符號都和它的宣告或使用資訊繫結在一起,比如其資料型別,作用於以及記憶體地址。const由於沒有了儲存和讀記憶體的操作,使得它的效率也很高。
0x01<<2+3;//由於+號的優先順序大於<<或>>符號,因此結果為0x01左移5位,值為32,一個整形長度位32位,左移或右移總位數不能超過32位。
第四章:指標與陣列
*p.f=>*(p.f)//因為.的優先順序高於*,因此該句的語義為對p取f偏移作為指標,然後指標去解引用。解引用就是取值的意思。
int *ap[]=>int *(ap[])=>(int *)(ap[])//因為[]的優先順序高於*,因此ap是一個元素為int指標的陣列。
int (*ap)[]//就是指向陣列的指標。
int *fp()=>int *(fp())//因為()的優先順序高於*,因此fp是一個函式,返回int *型別,也就是返回指標的函式。函式指標指向函式的入口地址。
int (*fp)()//fp為一個指向函式的指標,也就是函式的指標,這裡函式引數為空,返回值為int。
++和--的優先順序高於*
練習:
1.int (*(*d)[5])(int *);
*d是一個指標,指標指向一個含有5個元素的陣列,每一個元素是一個函式指標,函式引數為int*,返回值為int。
2.int (*(*e)(int*))[5];
*e是一個指標,這個指標是一個函式指標,形參為int*,返回一個指向陣列的指標,這個指標是整型的。
我們需要改為:
typedef int (*pArr)[5];//這裡的這個括號說明這是一個函式指標陣列,int為返回值。如果沒有括號這是就是別名的意思。
typedef pArr (*func)(int*);
char a[5] = { 'A', 'B', 'C', 'D', 'E' };
char(*p3)[5] = &a;//這裡因為左右相等的原則,只能以char(*p3)[5]的形式,其他都不可以,這裡的p3是指整個陣列,所以*(p3+1)指的是E後面的那個字元,也就是無效空字元。
#include<stdio.h>
int main()
{
char a[5] = { 'A', 'B', 'C', 'D', 'E' };
char(*p3)[5] = &a;
//char(*p4)[5] = a;
char *p4=*(p3 + 1);
return 0;
}
int *p=NULL;
和
int *p;
*p=NULL;//這個可能會不安全,因為p是一個未知地址,比較好的方法是
int i=10;
int *p=&i;
*p=NULL;
如何給一個地址存入值:
int *p=(int *)0x12ff7c;//如何判斷一個記憶體是否可訪問:int i=0;去到記憶體中取&i的值即可。但其實這種寫法在VS上會報錯,最好還是別用這種方法,說是因為偵錯程式會保護要寫區域的記憶體不允許訪問。
*p=0x100;//將值100存入0x12ff7c
或者直接:*(int *)0x12ff7c=0x100;
#include <iostream>
using namespace std;
struct Test
{
int Num;
char *pcName;
short sDate;
char ch[2];
short str[4];
}*p;//值為20,轉為16進位制位14
void main()
{
p = (Test*)0x100000;
printf("p+0x01 -> 0x%08x\n", p+0x01);//+0x01等價於加了一整個陣列結構,值為0x100014
printf("(unsigned long)p+0x01 -> 0x%08x\n", (unsigned long)p+0x01);//只加一,0x100001
printf("(unsigned int*)p+0x01 -> 0x%08x\n", (unsigned int*)p+0x01);//加了四,0x100004
system("pause");
}
大端模式:
a[0] a[1] a[2] a[3]
1 2 3 4
0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x03 0x00 0x00 0x00 0x04
大端模式讀出來的值是0x00000100,也就是0x100(大端從左往右讀)
在32位的x86系統下,是小端模式——認為第一個位元組是最低位位元組
在記憶體中存放為:
a[0] a[1]
0x01 00 00 00 0x02 00 00 00
所以讀取為:0x02000000(小端從右往左讀)
二維陣列的定義為:
a=>&a[0]
a[i]=>&a[0]+i*sizeof(char)*4
a[i][j]=>&a[0]+i*sizeof(char)*4+j*sizeof(char)=>*(*(a+i)+j)
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//因為是小括號,所以a的值為a={1,3,5};
//int a[3][2]={{0,1},{2,3},{4,5}};這樣才是正確的取值
int *p;
p = a[0];//a[0]的值是{1,3}
printf("%d", p[0]);//p[0]的值為1
system("pause");
}
算記憶體最好的方法是畫記憶體佈局圖
#include<stdio.h>
int main()
{
int a[5][5];
int(*p)[4];
p = a;/*這裡如果沒有疾病的話是有問題的...報錯在於無法將int(*)[5]型別分配給int(*)[4],除非改為int (*p)[5],但這樣就體現不出意義了,編譯器為VS2013,可能是編譯器的問題。不同的編譯器的起始
值不一定為0。後面有提到應該把指標初始定為NULL,就是把它拴在尺子的0mm處,估計是因為沒有拴指標導致的問題,看下之後第五章有沒有解決辦法。*/
printf("a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
printf("%p,%d", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
但其實這裡最妙的地方在於int(*p)[4]的含義,它是指int[][4],這裡的p++是以4個單位一起加的,結果為a[1][0],但如果int *p這裡的p++就是一個一個加的,也就是a[0][1]。
#include<stdio.h>
int main()
{
int a[3][4];
int(*p)[4];//int *p;p=a;這玩意也跑不了啊,總而言之思想記住就好。
p = a;//如果這樣跑的話這裡就ok
printf("a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
printf("%p,%d", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
無法向函式傳遞一個數組:
無法向函式傳遞指標變數本身,而是指標變數本身的拷貝值
這樣問題就來了,當我需要寫一個GetMemory的函式,這個函式負責向記憶體申請所需的空間。
#include<iostream>
using namespace std;
char *GetMemory(char *p, int num)
{
p = (char*)malloc(num*sizeof(char));
return p;
}
int main()
{
char*str = NULL;
str=GetMemory(str, 10);
/*如果以函式void形式不加return返回值則無效,因為指標本身不會被傳遞到函式中,而是以_str的拷貝形式傳進去的,然後這個_str在函式結束後又釋放掉了,因此str還是老樣子為NULL,而_str又在堆裡自動銷燬了。
唯一的用處是以指標指向_str的地址,將其拷貝出來並返回,這樣main裡才可以查詢到這個值。也就是對於要傳指標的函式一定要將這個指標傳出,否則無法運用到這個指標。*/
strcpy_s(str,6, "hello");
cout << str;
free(str);
system("pause");
}
#include<iostream>
using namespace std;
char **GetMemory(char **p, int num)
{
*p = (char*)malloc(num*sizeof(char));
return p;
}
int main()
{
char*str = NULL;
GetMemory(&str, 10);//這裡真真正正傳的是str的值,而不是通過_str傳的
strcpy_s(str,6, "hello");
cout << str;
free(str);
system("pause");
}
char a[3][4]等效的指標引數為char (*p)[10]
char *a[5]等效的指標引數為char **p
一箇中括號可以抵掉一個*號
函式指標:
char *fun(char* p1,char* p2){}
main:
char *(*pf)(char* p1,char* p2);
pf=&fun;
*pf("aa","bb");
#include<stdio.h>
void Function()
{
printf("Call Function!\n");
}
int main()
{
void (*p)();
*(int*)&p=(int)Function;//強制將Function從void型轉化為int型
(*p)();//這句話相當於呼叫了函式Function
return 0;
}
函式指標的主要作用在於
1.實現面向物件程式設計中的多型性
ps:很好的程式碼https://blog.csdn.net/philip_puma/article/details/25973139
2.回撥函式
typedef void (*event_handler) (unsigned int para1, unsigned int para2);
struct event {
unsigned int ev_id;
event_handler handler;
};
struct event event_queue[MAX_EVENT_SIZE];
這裡要搞懂怕是要去看原始碼了,這種坑不急著開。。。水平還不夠。。。
像事件epoll原始碼裡肯定是有的,紅黑樹那一塊會用到。
/*class A{void fun();};
class B{void fun();};
list<EventHandler> _lstEventHandlers;
A a1,a2;B b;
_lstEventHandlers.push_back(a1.fun);
_lstEventHandlers.push_back(a2.fun);
_lstEventHandlers.push_back(b.fun);*/
這個程式是一個事件handle函式,即回撥函式,在事件佇列中都是用一個函式指標來儲存的,程式可以通過掃
描這個事件佇列來獲取每個事件對應的處理函式,然後呼叫它。
成員變數這裡再加一條:
#include<iostream>
using namespace std;
class A{
public:
int x;
};
typedef int A::* MemberPointer;
int main(int argc, char *argv[])
{
MemberPointer pV;//成員變數指標的定義,等價於int A::* pV;
pV = &A::x;
A a;
a.*pV = 1;//等價於a.x=1;
cout << &a->*pV << endl;
//這幾種方式都是等價的
system("pause");
}
#include<stdio.h>
#include<string.h>
char* fun1(char* p)
{
printf("%s\n",p);
return p;
}
char* fun2(char* p)
{
printf("%s\n",p);
return p;
}
char* fun3(char* p)
{
printf("%s\n",p);
return p;
}
int main()
{
char* (*a[3])(char* p);
char* (*(*pf[3]))(char* p);
pf=&a;
a[0]=fun1;
a[1]=&fun2;
a[2]=&fun3;
pf[0][0]("fun1");//等價於(*pf)[0]("fun1");
pf[0][1]("fun2");//等價於(*pf)[1]("fun2");
pf[0][2]("fun3");//等價於(*pf)[2]("fun3");
return 0;
}
第五章:記憶體管理
棧的效率要比堆的效率高,因為棧是編譯時分配空間的,而堆是執行時動態分配空間的,且cpu有專門的暫存器(esp,ebp)來操作棧,而堆都是使用間接定址。棧的存取速度僅次於暫存器,並且棧內可以資料共享。
struct的記憶體對齊是按照當前結構體中最大的資料的位元組對齊的。有double或64位機子上有long,都是按8位元組對齊的。
struct結構體只需要對指標進行記憶體分配,只要結構體內有指標就需要進行記憶體分配。
舉例:
zclReportCmd_t *reportCmd;
typedef struct
{
uint8 numAttr;
zclReport_t attrList[];
}zclReportCmd_t;
typedef struct
{
uint16 attrID;
uint8 dataType;
uint8 *attrData;
}zclReport_t;
reportCmd=(zclReportCmd_t *)osal_msg_allocate(sizeof(zclReportCmd_t)+sizeof(zclRepor_t));
reportCmd->attrList[0].attrData=(uint8*)osal_msg_allocate(len);
再來一個:
struct image
{
struct header *info;
unsigned char **data;
};
struct image *newimage(int nr,int nc)
{
struct image *x;
x=(struct image *)malloc(sizeof(struct image));//image的指標
x->info=(struct header *)malloc(sizeof(struct header));//info指標
x->data=(unsigned char **)malloc(sizeof(unsigned char *)*nr);//data的第一層指標
x->data[0]=(unsigned char *)malloc(nr*nc);//data的第二層指標
for(i=0;i<nr;i++)
{
x->data[i]=(x->data+nr*i);
}
return x;
}
為char*分配指標記憶體
char *p1="abcdefg";
char *p2=(char*)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));//這裡需要考慮到'\0'的記憶體空間.
//並且字串常量需要以\0結束,例如char a[7]={'a','b','c','d','e','f','g'};這個就是未結束,需要多加\0。
//不要因為sizeof(char)為1就省略這個寫法,這樣只會使程式碼可以執行下降。
strcpy(p2,p1);//但其實strcpy這個函式已經會報錯了,需要改為strcpy_s(p2,8,p1);
初始化很重要,不確定的話就設為0或者設為NULL。
記憶體越界陣列加一減一問題,以及記憶體洩漏的問題。會產生洩漏的記憶體是堆上的記憶體(還有資源或控制代碼等的洩漏情況),也就是由malloc系列函式或者new操作符分配的函式。如果使用完後沒有及時free或delete,這塊記憶體就無法釋放,直到整個程式終止。
malloc的函式原型是(void*)malloc(int size);由於malloc會有不成功的可能性,因此一定要加入if(NULL!=p)的語句驗證。
char *p=(char*)malloc(int size);
free(p);
p=NULL;//一定要將釋放的指標設為空,不然就會變成懸垂指標。
第六章:函式
組合語言程式碼往往就是從入口處開始一條一條執行,直到遇到跳轉指令比如ARM的B、BL、BX、BLX。
編寫strlen函式,用遞迴的方式解決:
遞迴1.0
int my_strlen(const char* strDest)
{
assert(NULL!=strDest);
if('\0'==*strDest)
{
return 0;
}
else
{
return(1+my_strlen(++strDest));
}
}
遞迴2.0
int my_strlen(const char* strDest)
{
assert(NULL!=strDest);
return ('\0'!=*strDest)?(1+my_strlen(strDest+1)):0;
}
遞迴的效率低是因為每次的函式呼叫的開銷比迴圈來說大得多,而且遞迴深度太大可能會出現棧溢位的錯誤。