# C 編程,第 1 部分:簡介
> 原文:[Processes, Part 1: Introduction](https://github.com/angrave/SystemProgramming/wiki/Processes%2C-Part-1%3A-Introduction)
> 校驗:[青鳥](https://github.com/blue-bird1)
> 自豪地采用[谷歌翻譯](https://translate.google.cn/)
## 快速認識一下 C?
* 請繼續閱讀下面的 C 速成課程
* 然后查看 [C Gotchas wiki 頁面](https://cs241.apachecn.org/#/docs/10)。
* 并了解[文本 I/O](https://cs241.apachecn.org/#/docs/9) 。
* 使用 [Lawrence 的介紹視頻](http://cs-education.github.io/sys/#)放松一下(還有一個在瀏覽器中的虛擬機可以玩!)
## 外部資源
* [在 Y 分鐘中學習 X](https://learnxinyminutes.com/docs/c/) (強烈推薦瀏覽!)
* [面向 C ++ / Java 程序員的 C](http://www.ccs.neu.edu/course/com3620/parent/C-for-Java-C++/c-for-c++-alt.html)
* [Brian Kernighan 的 C 教程](http://www.lysator.liu.se/c/bwk-tutor.html)
* [c faq](http://c-faq.com/)
* [C 訓練營](http://gribblelab.org/CBootCamp/index.html)
* [C / C ++函數參考](http://www.cplusplus.com/reference/clibrary/)
* [gdb(Gnu 調試器)教程](http://www.unknownroad.com/rtfm/gdbtut/gdbtoc.html) 提示:使用“-tui”命令行參數運行 gdb 以獲得調試器的全屏版本。
* 在這里添加您喜歡的資源
## C 速成教程介紹
__ 新頁面警告 __ 請為我修復拼寫錯誤和格式錯誤,并添加有用的鏈接。*
### 你怎么用 C 寫一個完整的 hello world 程序?
```c
#include <stdio.h>
int main(void) {
printf("Hello World\n");
return 0;
}
```
### 為什么我們使用'`#include <stdio.h>;`'?
我們很懶!我們不想聲明`printf`函數。它已經在文件'`stdio.h`'中為我們完成了。 `#include`引入文件的文本內容將作為我們要編譯的文件的一部分。
具體來說,`#include`指令采用文件`stdio.h`(它代表標準輸入和輸出)位于操作系統的某個位置,復制文本,并將其替換為`#include`所在的位置。
### C 字符串是如何表示的?
它們在內存中表示為字符。字符串的結尾包括 NULL(0)字節。所以“ABC”需要四(4)個字節`['A','B','C','\0']`。找出 C 字符串長度的唯一方法是繼續讀取內存,直到找到 NULL 字節為止。 C 字符在內存中總是恰好一個字節。
在表達式中編寫字符串文字`"ABC"`時,字符串文字將計算為 char 指針(`char *`),該指針指向字符串的第一個字節/字符。這意味著下面示例中的`ptr`將保存字符串中第一個字符的內存地址。
```c
char *ptr = "ABC"
```
下面是一些常用的初始化字符串的方式:
```c
char *str = "ABC"
char str[] = "ABC"
char str[] = {"A", "B", "C", "\0"};
```
### 你如何聲明一個指針?
指針指的是內存地址。指針的類型很有用 - 它告訴編譯器需要讀/寫多少字節。您可以按如下方式聲明指針。
```c
int *ptr1;
char *ptr2;
```
由于 C 的語法,`int*`或任何指針實際上不是它自己的類型。您必須在每個指針變量前面加上星號。作為一個常見的問題,以下
```c
int* ptr3, ptr4;
```
只會將`*ptr3`聲明為指針。 `ptr4`實際上是一個常規的 int 變量。要修復此聲明,請將`*`保留在指針前面
```c
int *ptr3, *ptr4;
```
### 你如何使用指針來讀/寫一些內存?
假設我們聲明了一個指針`int *ptr`。為了便于討論,我們假設`ptr`指向內存地址`0x1000`。如果我們想寫一個指針,我們可以順從并分配`*ptr`。
```c
*ptr = 0; // Writes some memory.
```
C 將做的是指針的類型`int`并從指針的開頭寫入`sizeof(int)`字節,這意味著字節`0x1000`,`0x1004`,`0x1008`,`0x100a`將全部為零。寫入的字節數取決于指針類型。對于所有原始類型都是一樣的,但結構有點不同。
### 什么是指針算術?
您可以向指針添加整數。但是,指針類型用于確定增加指針的數量。對于 char 指針,這是微不足道的,因為字符總是一個字節:
```c
char *ptr = "Hello"; // ptr 保存 'H'的內存位置
ptr += 2; //ptr 現在指向 'l'
```
如果 int 是 4 個字節,那么 ptr + 1 指向 ptr 指針往后移動 4 個字節的位置。
```c
char *ptr = "ABCDEFGH";
int *bna = (int *) ptr;
bna +=1; // 每次迭代一個int類型的內存大小(在某些機器上是4個字節)
ptr = (char *) bna;
printf("%s", ptr);
/* 注意到只有“EFGH”打印出來了。這是為什么呢?就是我們剛剛提到的,當操作“bna+=1"時,我們對int指針增加了1(在大多數機器是4個字節),這就等于4個字符(每個字符一個字節)。
return 0;
```
因為 C 中的指針運算總是按指向的類型的大小自動縮放,所以不能對 void 指針執行指針運算。
您可以將 C 中的指針算法視為基本上執行以下操作
如果我想做
```c
int *ptr1 = ...;
int *offset = ptr1 + 4;
```
想象
```c
int *ptr1 = ...;
char *temp_ptr1 = (char*) ptr1;
int *offset = (int*)(temp_ptr1 + sizeof(int)*4);
```
去得到這個值。 **每次進行指針運算時,請深呼吸并確保您正在移動您認為正在轉換的字節數。**
### 什么是 void 指針?
沒有類型的指針(非常類似于 void 變量)。當您正在處理的數據類型未知或者您正在使用其他編程語言連接 C 代碼時,將使用 Void 指針。您可以將其視為原始指針,或僅僅是內存地址。您無法直接讀取或寫入它,因為 void 類型沒有大小。例如
```c
void *give_me_space = malloc(10);
char *string = give_me_space;
```
這不需要強制轉換,因為 C 會自動將`void*`提升為適當的類型。 **注:**
gcc 和 clang 不符合 ISO-C 標準,這意味著它們可以讓你對 void 指針進行算術運算。他們會將它視為 char 指針,但不要這樣做,因為它可能不適用于所有編譯器!
### `printf`調用 write 或 write 調用`printf`?
`printf`調用`write`。 `printf`包含一個內部緩沖區,因此,為了提高性能`printf`每次調用`printf`時都不會調用`write`。 `printf`是 C 庫函數。 `write`是系統調用,我們知道系統調用很昂貴。另一方面,`printf`使用的緩沖區在這一點上更適合我們的需求
## 你如何打印指針值?整數?字符串?
使用格式說明符“%p”表示指針,“%d”表示整數,“%s”表示字符串。所有格式說明符的完整列表可以在[這里](http://www.cplusplus.com/reference/cstdio/printf/)找到
整數示例:
```c
int num1 = 10;
printf("%d", num1); //打印num1的值
```
整數指針的示例:
```c
int *ptr = (int *) malloc(sizeof(int));
*ptr = 10;
printf("%p\n", ptr); //打印指針指向的地址
printf("%p\n", &ptr); /* 打印指針本身的地址,在處理雙指針的問題時非常有用 */
printf("%d", *ptr); //打印指針指向的地址的內容
return 0;
```
字符串示例:
```c
char *str = (char *) malloc(256 * sizeof(char));
strcpy(str, "Hello there!");
printf("%p\n", str); // 打印堆的地址
printf("%s", str);
return 0;
```
[作為指針和數組的字符串](https://www.cs.bu.edu/teaching/c/string/intro/)
### 如何將標準輸出保存到文件中?
最簡單的方法:運行程序并使用 shell 重定向,例如
```
./program > output.txt
#To read the contents of the file,
cat output.txt
```
更復雜的方法:close(1)然后使用 open 重新打開文件描述符。見 [http://cs-education.github.io/sys/#chapter/0/section/3/activity/0](http://cs-education.github.io/sys/#chapter/0/section/3/activity/0)
### 指針和數組之間有什么區別?舉一個你可以用一個而不是另一個做的事情的例子。
```c
char ary[] = "Hello";
char *ptr = "Hello";
```
例
數組名稱指向數組的第一個字節。 `ary`和`ptr`都可以打印出來:
```c
char ary[] = "Hello";
char *ptr = "Hello";
// Print out address and contents
printf("%p : %s\n", ary, ary);
printf("%p : %s\n", ptr, ptr);
```
該數組是可變的,因此我們可以更改其內容(注意不要在數組末尾之外寫入字節)。幸運的是“World”不比“Hello”更長
在這種情況下,char 指針`ptr`指向某個只讀存儲器(存儲靜態分配的字符串文字),因此我們無法更改這些內容。
```c
strcpy(ary, "World"); // OK
strcpy(ptr, "World"); // NOT OK - Segmentation fault (crashes)
```
但是,與數組不同,我們可以將`ptr`更改為指向另一塊內存,
```c
ptr = "World"; // OK!
ptr = ary; // OK!
ary = (..anything..) ; // 不能通過編譯
// ary只能指向原數組。
printf("%p : %s\n", ptr, ptr);
strcpy(ptr, "World"); // OK, ptr現在指向可變的內存(數組)
```
從中可以看出,指針*可以指向任何類型的內存,而 C 數組[]只能指向棧上的內存。在更常見的情況下,指針將指向堆內存,在這種情況下,指針 CAN 引用的內存可以被修改。
### `sizeof()`返回字節數。所以使用上面的代碼,sizeof(ary)和 sizeof(ptr)是什么?
`sizeof(ary)`:`ary`是一個數組。返回整個數組所需的字節數(5 個字符+ 零字節= 6 個字節)`sizeof(ptr)`:與 sizeof(char *)相同。返回指針所需的字節數(例如,對于 32 位或 64 位機器,為 4 或 8)
`sizeof`是一個特殊的操作。實際上,編譯器在編譯程序之前會替換它,因為所有類型的大小在編譯時都是已知的。當你的`sizeof(char*)`占用機器上指針的大小時(64 位機器為 8 字節,32 位為 4,依此類推)。當您嘗試`sizeof(char[])`時,編譯器會查看它并替換**整個**數組包含的字節數,因為在編譯時已知數組的總大小。
```c
char str1[] = "will be 11";
char* str2 = "will be 8";
sizeof(str1) //11 因為它是一個數組
sizeof(str2) //8 因為它是一個指針
```
使用 sizeof 作為字符串的長度要小心!
### 以下哪些代碼正確么,為什么?
```c
int* f1(int *p) {
*p = 42;
return p;
} // 正確;
```
```c
char* f2() {
char p[] = "Hello";
return p;
} // 錯誤!
```
說明:在棧上創建一個數組 p,其大小正確以容納 H,e,l,l,o 和空字節,即(6)字節。這個數組存儲在棧中,從 f2 返回后無效。
```c
char* f3() {
char *p = "Hello";
return p;
} // OK
```
說明:p 是指針。它保存字符串常量的地址。字符串常量不變,即使在 f3 返回后也有效。
```c
char* f4() {
static char p[] = "Hello";
return p;
} // OK
```
說明:數組是靜態的,意味著它在進程的生命周期中存在(靜態變量不在堆或棧上)。
### 你如何查詢 C 庫調用和系統調用的信息?
使用手冊頁。請注意,手冊頁按部分組織。第 2 節=系統調用。第 3 節= C 庫。 Web:Google“man7 open”shell:man -S2 open 或 man -S3 printf
### 你如何在堆上分配內存?
使用 malloc。還有 realloc 和 calloc。通常與 sizeof 一起使用。例如足夠的空間來容納 10 個整數
```c
int *space = malloc(sizeof(int) * 10);
```
### 這個字符串復制代碼有什么問題?
```c
void mystrcpy(char*dest, char* src) {
// void 表示沒有返回值
while( *src ) { dest = src; src ++; dest++; }
}
```
在上面的代碼中,它只是將 dest 指針更改為指向源字符串。也不復制 nuls 字節。這是一個更好的版本 -
```
while( *src ) { *dest = *src; src ++; dest++; }
*dest = *src;
```
注意,通常會看到以下類型的實現,它執行表達式測試中的所有操作,包括復制 nul 字節。
```c
while( (*dest++ = *src++ )) {};
```
### 你怎么寫 strdup 的替換?
```c
// Use strlen+1 to find the zero byte...
char* mystrdup(char*source) {
char *p = (char *) malloc ( strlen(source)+1 );
strcpy(p,source);
return p;
}
```
### 你如何在堆上取消分配內存?
使用 free!
```c
int *n = (int *) malloc(sizeof(int));
*n = 10;
//Do some work
free(n);
```
### 什么是二次釋放錯誤?你怎么能避免?什么是懸垂指針?你怎么避免?
二次釋放錯誤是指您不小心嘗試兩次釋放相同的內存。
```c
int *p = malloc(sizeof(int));
free(p);
*p = 123; // Oops! - 危險的指針! 寫入了不存在的內存
free(p); // Oops! - 二次釋放!
```
要修復首先是編寫正確的程序!其次,一旦內存被釋放,重置指針是很好的編程風格。這可以確保指針無法正確使用而不會導致程序崩潰。
修復方法:
```c
p = NULL; // 現在你不會犯錯了使用這個指針
```
### 什么是緩沖區溢出的例子?
著名的例子:Heart Bleed(在一個大小不足的緩沖區中執行 memcpy)。簡單的例子:在確定所需內存的大小時,實現 strcpy 時忘記對strlen 添加 1。
### 什么是'typedef',你如何使用它?
這是聲明類型的別名。通常與`structs`一起使用以減少必須將'struct'作為類型的一部分進行編寫的視覺混亂。
```c
typedef float real;
real gravity = 10;
// Also typedef gives us an abstraction over the underlying type used.
// For example in the future we only need to change this typedef if we
// wanted our physics library to use doubles instead of floats.
typedef struct link link_t;
//With structs, include the keyword 'struct' as part of the original types
```
在本課程中,我們經常使用 typedef 定義一個函數。例如,函數的 typedef 就是這樣
```c
typedef int (*comparator)(void*,void*);
int greater_than(void* a, void* b){
return a > b;
}
comparator gt = greater_than;
```
這聲明了一個函數類型比較器,它接受兩個`void*`參數并返回一個 int。
### 哇 這就足夠理解C語言了
不要再擔心接下來的知識了!
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話