---
# System prepended metadata

title: C語言的特性
tags: [語法]

---

---
title: C語言的特性
description: 介紹C語言這個語言界的毒瘤(X
tags: 語法
robots: noindex, nofollow
langs: zh
---
# 編譯
先不說了，上圖www
![Compile steps (Ntu Version)](https://www3.ntu.edu.sg/home/ehchua/programming/cpp/images/GCC_CompilationProcess.png)
![Compile steps (Laura Roudge ver.)](https://miro.medium.com/max/518/1*wHKe6W4opLmk6pb7sxZz6w.png)
我個人比較喜歡下面那張圖，不過因為他是放在Medium上的，怕哪天就死圖了QQ
但基本上我們今天的重點會集中在
- Pre-Processor

然後我不想講Compiler，因為我也不會:(
## Pre-Processor
其實這東西我會選擇把他解讀成一個「自動的複製貼上機」
Pre-Processor工作的方式很簡單
就是看到這行開頭為"#"就開始解讀他該做什麼
主要常見的就是```#include```跟```#define```
### include
include 進來的東西都會在編譯時期加入到你的程式碼一起編譯    白話文就是複製貼上啊XDDD
### define 
範例如下
```cpp
#include <cstdio>
#define HI printf("Hello World\n")
#define RESP(x) printf("%s\n", x)
int main(){
    HI;
    RESP("World is not going to say hello back.\n");
    return 0;
}
```
Pre-Processor此時的工作很簡單
看到有空格隔開的HI就把他替換成printf("Hello World\n")
看到有空格格開的RESP(?)且括號裡面有些什麼
就把x的地方替換成?的內容

白話文還是    複製貼上機

## Linking
本來還想寫，還想講的，但後來...
\_(:3」∠)\_
我懶 : )

# 未定義行為(Undefined Behavior)
大家討厭C語言就在這裡了，未定義行為(簡稱UB)就是說當你做出不合C語言規範的行為時，編譯器與你的程式有權利做任何的行為，不管是讓你程式假裝跑出對的答案還是把你的電腦給燒了(? 都是可以的
舉幾個未定義行為
## 函數該回傳不回傳
比如說
```cpp
#include <cstdio>
int hello(int n){
    printf("%d\n", n);
}
int main(){
    printf("%d", hello(10));
    return 0;
}
```
此時....就算是一種未定義行為了
曾經clang(某編譯器)就曾有遇到這種狀況發生時，整支程式會亂跳亂跑....
## 存取未初始化得值
比如
```cpp
#include <cstdio>
int main(){
    int a;
    printf("%d", a);
}
```
此時a要是多少都可以，編譯器沒有責任幫你把a設為0
## 溢位(Overflow/ Downflow)
如果你的變數出現溢位的話，編譯器可能再優化期間亂搞你的程式
```cpp
#include <cstdio>
int main(){
    int a = INT_MAX;
    if(a+1 > a){
        printf("Oh No\n");
    }
    else{
        printf("Oh Yes\n");
    }
    return 0;
}
```
即使a+1已經溢位了，也就是說現在的a+1應該是-2147483648
但因為編譯時期的優化(實際看優化狀況而定)，編譯器可能會把```if(a+1>a)```優化成```if(true)```因為$a+1$在沒有溢位的狀況下必大於$a$
## 有號與無號變數相比較
如例
```cpp
#include <cstdio>
int main(){
    int a = -1
    unsigned b = 1;
    printf("%d", a>b);
}
```
這隻程式很有可能會輸出1
因為-1在記憶體中表示成0xffffffff
而無號的1在記憶體中表示成0x00000001
所以執行階段時很可能會是0xffffffff > 0x00000001
而編譯器也沒有義務救你QQ，可能只會看著你爛

總之寫C/C++要十分小心自己寫的每一行程式，清晰地知道自己在幹嘛，不然等等被橫空飛出來的不知道什麼東西陰了也找不到人算帳QQ
## 存取非法記憶體空間
比如說
```cpp 
#include <cstdio>
using namespace std;
int main(){
    int arr[10] = {0};
    printf("%d\n", arr[10]);
    return 0;
}
```
此時程式很有可能輸出任意數字而不出錯，也可能出現Segment Fault並直接退出
# 執行
## Memory Layout
![Memory Layout](https://media.geeksforgeeks.org/wp-content/uploads/memoryLayoutC.jpg)
Memory Layout是一支程式所能存取到的記憶體的編排方式，在多數現代作業系統與執行平台的架構中，是由虛擬記憶體的方式建構的，也就是說看似連續的記憶題，在實體記憶裝置中是離散式儲存的(甚至可能一半以壓縮形式儲存在RAM裡，另一塊在硬碟的置換空間中也說不定)
- text
儲存程式位元碼的記憶體區段，一般而言是唯獨的(事實上看cpu架構而言，[維基百科連結](https://zh.wikipedia.org/wiki/NX位元))
- Initialized Data
這裡所儲存的就是宣告時已賦值的全域變數，這裡事實上不是唯獨的
- BSS(uninitialized data)
這裡所儲存的就是宣告時未賦值的全域變數，**他會被系統初始化為0x00**
- Stack(棧)
這裡是所有程式執行時呼叫的函數所帶有的區域變數，詳細的在下個小節中會繼續說明
- Heap(堆)
整隻程式中動態分配的空間都會出現在這裡
## 函數的執行
函數可以一層一層的呼叫，甚至可以遞迴執行，事實上是因為函數的執行建構在Stack這個資料結構上。在函數被呼叫時，這一層函數執行時所需的區域變數空間便會被放入Stack中，當函數執行完畢後，便會從Stack中移除(事實上，這些空間的「移除」並不是真的清空，而是視而不見，這也就是為什麼區域變數一定要初始化，不然你可能讀到剛剛使用這個空間的函數內的區域變數)
很有趣的是，程式的進入點main本身也是一個函數，他的回傳值應該要回傳給啟動這隻程式的程式，而多數系統的慣例就是只要回傳值為0(事實上也有系統的標準不是這樣子的，所以stdlib裡有個define叫EXIT_SUCCESS)，就代表程式正常退出。有些Online Judge可能會因為你的main回傳了非0的值而不給你AC(比如Google Code Jam跟Kattis都會這樣)，所以乖乖寫return是好習慣！
# 變數的生命週期
## BSS & Initialized Data
全域變數的生命週期從程式的開始到結束都會存在
## Stack
當一個函數被呼叫時，其所含的區域變數會在呼叫時分配進Stack的空間，並在函數結束時解構。這會導致如果你回傳了區域變數的話，在這個指標在函數外解開時會導致未定義行為的發生。
例如
```cpp
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
int *hello(){
    int a = rand();
    return &a;
}
int main(){
    srand(time(NULL));
    int *a = hello();
    printf("%d\n", *a);
    hello();
    printf("%d\n", *a);
    return 0;
}
```
可以發現上面的程式會輸出兩行不一樣的數字！
## Heap
從你把它new出來，直到你把他delete或是程式結束自動由系統清除。其空間分配仰賴Heap這種資料結構，因而得名。
