1. 程式人生 > >C語言深度解剖----重點部分筆記

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;    
}

遞迴的效率低是因為每次的函式呼叫的開銷比迴圈來說大得多,而且遞迴深度太大可能會出現棧溢位的錯誤。