C語言學習_內存分區

1.1 數據類型
  • 數據類型的本質:固定內存大小的別名
  • 數據類型的做用:編譯器預算對象(變量)分配的內存空間大小c++

    int a;  // 告訴編譯器分配4個字節的內存
  • 數據類型能夠經過typedef起別名
  • 數據類型能夠經過sizeof()測類型大小
  • void數據類型(無類型,萬能類型)程序員

    • 若是函數沒有返回值,必須用void修飾數組

      // 對函數返回的限定
      void fun(int a);
    • 若是函數沒有參數,參數能夠用void修飾函數

      // 對函數參數的限定
      int fun(void);
    • 不能定義void類型的普通變量spa

      void a; // error,不能肯定分配內存空間的大小
    • 萬能指針操作系統

      void * p; // ok, 萬能指針,指針類型都是4個字節,函數參數,函數有返回值
      //1. void* 能夠指向任何類型的數據,被稱爲萬能指針
      void test03()
      {
        int a = 10;
        void* p = NULL;
        p=&a;
        printf("a:%d\n",*(int*)p);
        char c = 'a';
        p=&c;
        printf("c:%c\n",*(char*)p);
      }
      //2. void* 經常使用於數據類型的封裝
      void test04()
      {
        void * memcpy(void * _Dst, const void * _Src, size_t _Size);
      }
  • sizeof操做符注意事項3d

    • sizeof返回的佔用空間大小是爲這個變量開闢的大小,而不僅是它用到的空間。因此對結構體用的時候,大多狀況下就得考慮字節對齊的問題了;
    • sizeof返回的數據結果類型是unsigned int
    • 要注意數組名和指針變量的區別。一般狀況下,咱們總以爲數組名和指針變量差不 多,可是在用sizeof的時候差異很大:指針

      • 對數組名用sizeof返回的是整個數組的大小
      • 對指針變量進行操做的時候返回的則是指針變量自己所佔得空間,在32位機的條件下通常都是4。
      • 當數組名做爲函數參數時,在函數內部,形參也就是個指針,因此再也不返回數組的大小

示例代碼:code

//1. sizeof基本用法
void test01()
{
  int a = 10;
  printf("len:%d\n", sizeof(a));
  printf("len:%d\n", sizeof(int));
  printf("len:%d\n", sizeof a);
}

//2. sizeof 結果類型
void test02()
{
  unsigned int a = 10;
  if (a - 11 < 0)
  {
    printf("結果小於0\n");
  }
  else
  {
    printf("結果大於0\n");
  }
  int b = 5;
  if (sizeof(b) - 10 < 0)
  {
    printf("結果小於0\n");
  }
  else
  {
    printf("結果大於0\n");
  }
}

//3. sizeof 碰到數組
void TestArray(int arr[])
{
  printf("TestArray arr size:%d\n",sizeof(arr));
}
void test03()
{
  int arr[] = { 10, 20, 30, 40, 50 };
  printf("array size: %d\n",sizeof(arr));

  //數組名在某些狀況下等價於指針
  int* pArr = arr;
  printf("arr[2]:%d\n",pArr[2]);
  printf("array size: %d\n", sizeof(pArr));

  //數組作函數函數參數,將退化爲指針,在函數內部再也不返回數組大小
  TestArray(arr);
}
1.2 變量
1.2.1 變量的概念

​既能讀又能寫的內存對象,稱爲變量;對象

若一旦初始化後不能修改的對象則稱爲常量。

變量定義的形式:類型 標識符,標識符,…,標識符
1.2.2 變量的本質
  • 變量名的本質:一段連續內存空間的別名;
  • 程序經過變量來申請和命名內存空間int a = 0
  • 經過變量名訪問內存空間;
  • 不是向變量名讀寫數據,而是向變量所表明的內存空間中讀寫數據;
  • 變量的三要素:名稱、大小、做用域
  • 變量的生命週期:結合內存四區模型來看

修改變量的兩種方式:

void test()
{
    int a = 10;

    //1. 直接修改
    a = 20;
    printf("直接修改,a:%d\n",a);

    //2. 間接修改
    int* p = &a;
    *p = 30;

    printf("間接修改,a:%d\n", a);
}
1.3 程序的內存四區模型
1.3.1 內存分區
  • 程序運行以前:

    C程序編譯過程

    1)預處理:宏定義展開、頭文件展開、條件編譯,這裏並不會檢查語法

    2)編譯:檢查語法,將預處理後文件編譯生成彙編文件

    3)彙編:將彙編文件生成目標文件(二進制文件)

    4)連接:將目標文件連接爲可執行程序

    可執行程序內部已經分好3段信息,分別爲代碼區text、數據區data和未初始化數據區bss 3個部分(有些人直接把databss合起來叫作靜態區或全局區)。

    整體來說說,程序源代碼被編譯以後主要分紅兩種段:程序指令(代碼區)和程序數據(數據區)。代碼段屬於程序指令,而數據域段和.bss段屬於程序數據。

  • 程序運行以後:

    程序在加載到內存前,代碼區和全局區(data和bss)的大小就是固定的,程序運行期間不能改變。而後,運行可執行程序,操做系統把物理硬盤程序load(加載)到內存,除了根據可執行程序的信息分出代碼區(text)、數據區(data)和未初始化數據區(bss)以外,還額外增長了棧區堆區

總的來講,內存分區模型:

代碼區:可執行代碼段,不可修改。

未初始化數據區(BSS):全局未初始化,靜態未初始化數據,數據的生存週期爲整個程序運行過程 。

全局初始化數據區/靜態數據區(data segment):全局初始化,靜態初始化數據,文字常量(只讀),數據的生存週期爲整個程序運行過程。

棧區(stack):先進後出的內存結構,由編譯器自動分配釋放 ,存放函數的參數值、返回值、局部變量等

堆區(heap):容量要遠遠大於棧,用於動態內存分配。堆在內存中位於BSS區和棧區之間。通常由程序員分配和釋放,若程序員不釋放,程序結束時由操做系統回收。

1.3.2 分區模型
  • 棧區

    由系統進行的內存管理,主要存放函數的參數以及局部變量,函數完成後系統自動釋放。

    #char* func()
    {
      char p[] = "hello world!"; //在棧區存儲 亂碼
      printf("%s\n", p);
      return p;
    }
    void test()
    {
      char* p = NULL;
      p=func();  
      printf("%s\n",p);
    }
  • 堆區

    手工申請,手工釋放

    char* func()
    {
      char* str = malloc(100);
      strcpy(str, "hello world!");
      printf("%s\n",str);
      return str;
    }
    
    void test01()
    {
      char* p = NULL;
      p=func();
      printf("%s\n",p);
    }
    
    void allocateSpace(char* p)
    {
      p=malloc(100);
      strcpy(p, "hello world!");
      printf("%s\n", p);
    }
    
    void test02()
    {
      char* p = NULL;
      allocateSpace(p);
    
      printf("%s\n", p);
    }

    圖片描述

    堆分配內存API:

    void *calloc(size_t nmemb, size_t size);
    /*
    * 功能:
    * 在內存動態存儲區中分配nmemb塊長度爲size字節的連續區域。calloc自動將分配的內存置0。
    * 參數:
    * nmemb:所需內存單元數量
    * size:每一個內存單元的大小(單位:字節)
    * 返回值:
    *  成功:分配空間的起始地址
    *  失敗:NULL
    */
  • 全局/靜態區

    全局靜態區內的變量在編譯階段已經分配好內存空間並初始化。這塊內存在程序運行期間一直存在,它主要存儲全局變量靜態變量常量

    int v1 = 10;        //全局/靜態區
    const int v2 = 20;  //常量,一旦初始化,不可修改
    static int v3 = 20; //全局/靜態區
    char *p1;           //全局/靜態區,編譯器默認初始化爲NULL
    
    //那麼全局static int 和 全局int變量有什麼區別?
    
    void test()
    {
      static int v4 = 20; //全局/靜態區
    }
    
    // 加深理解
    char* func()
    {
      static char arr[] = "hello world!"; //在靜態區存儲 可讀可寫
      arr[2] = 'c';
      char* p = "hello world!"; //全局/靜態區-字符串常量區 
      //p[2] = 'c'; //只讀,不可修改 
      printf("%d\n",arr);
      printf("%d\n",p);
      printf("%s\n", arr);
      return arr;
    }
    void test()
    {
      char* p = func();
      printf("%s\n",p);
    }

總結:

數據區包括:堆,棧,全局/靜態存儲區。
全局/靜態存儲區包括:常量區,全局區、靜態區。
常量區包括:字符串常量區、常變量區。
代碼區:存放程序編譯後的二進制代碼,不可尋址區。

能夠說,C/C++內存分區其實只有兩個,即代碼區和數據區。

1.3.3 函數調用模型
int func(int a,int b)
{
  int t_a = a;
  int t_b = b;
  return t_a + t_b;
}

int main()
{
  int ret = 0;
  ret = func(10, 20);
  return EXIT_SUCCESS;
}

圖片描述

1.3.4 棧的生長方向和內存存放方向

圖片描述

//1. 棧的生長方向
void test01()
{

  int a = 10;
  int b = 20;
  int c = 30;
  int d = 40;

  printf("a = %d\n", &a);
  printf("b = %d\n", &b);
  printf("c = %d\n", &c);
  printf("d = %d\n", &d);

  //a的地址大於b的地址,故而生長方向向下
}

//2. 內存生長方向(小端模式)
void test02()
{
  //高位字節 -> 地位字節
  int num = 0xaabbccdd;
  unsigned char* p = &num;

  //從首地址開始的第一個字節
  printf("%x\n",*p);
  printf("%x\n", *(p + 1));
  printf("%x\n", *(p + 2));
  printf("%x\n", *(p + 3));
}
相關文章
相關標籤/搜索