# 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 编程:复习题
- 多线程编程:复习题
- 同步概念:复习题
- 记忆:复习题
- 管道:复习题
- 文件系统:复习题
- 网络:复习题
- 信号:复习题
- 系统编程笑话