# 18.2 基础 C 语法

本节不会涵盖 C 的所有内容，也不打算教你如何编写 C。它将重点介绍 C 的不同方面，或者在 Python 开发人员第一次看到它们时感到困惑的方面。

## 通用知识

与 Python 不同，空格对 C 编译器并不重要。编译器不在乎你是跨行拆分语句，还是把整个程序挤成一条很长的行。这是因为它对所有语句和块都使用分隔符。

当然，解析器有非常具体的规则，但一般来说，只要知道每个语句以分号（;）结尾，以及所有代码块都用大括号（{}）包围，你就能理解 CPython 源码。

此规则的例外情况是如果块只有一条语句，则可以省略大括号。

C 中的所有变量都必须被声明，这意味着需要有一条语句表示该变量的类型。请注意，与 Python 不同，单个变量可以容纳的数据类型不能更改。

让我们来看看一些例子：

代码不翻译

一般来说，你看到的 CPython 代码格式会非常整洁，并且通常在给定的模块中会坚持单一风格。

## if 语句

在 C 中，`if` 的工作方式通常与 Python 中的工作方式相同。如果条件为 true，则执行下面的代码块。Python 程序员应该非常熟悉 `else` 和 `elseif` 语法。请注意，C 中的 `if` 语句不需要使用 `endif`，因为代码块由 {} 来分隔。

`if...else` 语句在 C 中的简写被称为**三元运算符**：

```c
condition ? true_result : false_result
```

你可以在 `semaphore.c` 中找到它，对于 Windows 而言，它定义了 `SEM_CLOSE()` 宏：

```c
#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)
```

如果函数 `CloseHandle()` 返回 `true`，则此宏的返回值将为 `0`，否则为 `-1`。

{% hint style="info" %}
**注**

布尔变量类型在部分 CPython 源码中受支持和使用，但它们不是原始语言的一部分。C 使用简单的规则解释二进制条件：0 或 NULL 为假，其他一切都为真。
{% endhint %}

## switch 语句

与 Python 不同的是，C 还支持 `switch`。使用 `switch` 可以被视为扩展 `if...else` 链的快捷方式。此示例来自 semaphore.c：

代码不翻译

这将基于 `WaitForSingleObjectEx()` 的返回值执行切换。如果该值为 `WAIT_OBJECT_0`，则执行第一个代码块。`WAIT_TIMEOUT` 值会执行第二个代码块，其他任何内容都与 `default` 代码块匹配。

需要注意的是 `switch` 中的值都会被测试校验。在本例中，来自 `WaitForSingleObjectEx()` 的返回值必须是整数值或枚举类型，并且每一个 `case` 都必须是常量值。

## 循环

C 语言中有三种循环结构：

1. `for` 循环；
2. `while` 循环；
3. `do ... while` 循环。

让我们依次看一下每一个循环结构：

`for` 循环的语法与 `Python` 完全不同：

```c
for ( <initialization>; <condition>; <increment>) {
<code to be looped over>
}
```

除了要在循环中执行的代码外，还有三个代码块控制 for 循环：

1. 当循环启动时，`<initialization>` 部分正好运行一次。它通常用于将循环计数器设置为初始值（可能还用于声明循环计数器）。
2. `<increment>` 代码在每次通过循环的主代码块后立即运行。一般情况下，这将增加循环计数器。
3. 最后，`<condition>` 在 `<increment>` 之后运行。将计算此代码的返回值，当此条件返回 false 时，循环将中断。

下面是 `Modules/sha512module.c` 中的一个示例：

```c
for (i = 0; i < 8; ++i) {
    S[i] = sha_info->digest[i];
}
```

此循环将运行 8 次，`i` 从 0 递增到 7，当条件被检查且 `i` 为 8 时将终止。

`while` 循环实际上与 Python 对应的循环相同。但是，`do...while` 语法有点不同。到第一次执行循环体之后才会检查 `do...while` 循环中的条件。

在 CPython 代码基线中，有许多 `for` 循环和 `while` 循环实例，但是 `do..while` 没有被使用到。

## 函数

C 中的函数语法与 Python 中的语法相似，只是必须指定返回类型和参数类型。C 语法是这样的：

```c
<return_type> function_name(<parameters>) {
    <function_body>
}
```

返回类型可以是 C 中的任何有效类型，包括内置类型，如：`int` 和 `double`，以及自定义类型，如：`PyObject`，如来自 `semaphore.c` 中的示例所示：

```c
static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
    <statements of function body here>
}
```

在这里，你可以看到一些 C 相关的特性正在发挥作用。首先，记住空格并不重要。CPython 源代码的大部分内容都将函数的返回类型放在函数声明的上方。这就是 `PyObject *` 部分。稍后，你将仔细研究 `*` 的用法，但现在重要的是要知道，你可以在函数和变量上放置一些修饰符。

`static` 是这些修饰符之一。有一些复杂的规则来管理修饰符的操作方式。例如，这里的 `static` 修饰符的含义与将其放在变量声明前面时非常不同。

幸运的是，在尝试阅读和理解 CPython 源代码时，你通常可以忽略这些修饰符。

函数的参数列表是一个逗号分隔变量列表，类似于你在 Python 中使用的。同样，C 需要每个参数的具体类型，因此 `SemLockObject *self` 表示第一个参数是指向 `SemLockObject` 的指针，并被称为 `self`。请注意，C 中的所有参数都是位置参数。

让我们来看看该语句的“指针”部分是什么意思。

为了给出一些上下文，传递给 C 函数的参数都是按值传递的，这意味着函数在调用函数中的值的副本上操作，而不是在原始值上操作。为了解决这个问题，函数将经常传入函数可以修改的一些数据的地址。

这些地址被称为**指针**，并具有类型，因此 `int *` 是指向整数值的指针，与 double \* 类型不同，double \* 是指向双精度浮点数的指针。

## 指针

如上所述，指针是保存值地址的变量。这些在 C 中经常使用，如本示例所示：

```c
static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
    <statements of function body here>
}
```

在这里，`self` 参数将保存 `SemLockObject` 值的地址或指向 `SemLockObject` 值的指针。另外请注意，该函数将返回指向 `PyObject` 值的指针。

在 C 中，有一个名为 NULL 的特殊值，它表示指针不指向任何东西。在整个 CPython 源码中，你将看到分配给 NULL 并根据 NULL 进行检查的指针。需要注意的一点是指针可以具有什么值的限制很少，而且访问不属于程序的内存位置可能会导致非常奇怪的行为。

另一方面，如果你尝试访问 NULL 的内存，则你的程序将会立即退出。这似乎不太好，但访问 `NULL` 通常比修改随机内存地址更容易找出内存漏洞。

## 字符串

C 没有字符串类型。有一个惯例：许多标准库函数都是围绕它编写的，但没有实际的类型。相反，C 中的字符串存储为 `char` （对于 ASCII 而言）或 `wchar` （对于 Unicode 而言）值的数组，每个值都包含一个字符。字符串用**空终止符**标记，其值为 `0`，通常在代码中显示为`\0`。

基本的字符串操作，如 `strlen()` 依赖于此空终止符来标记字符串的结尾。

因为字符串只是值的数组，所以不能直接复制或比较它们。标准库有 `strcpy()` 和 `strcmp()` 函数（及其`wchar` 表亲）用于执行这些操作等。

## 结构体

在这个 C 迷你之旅的最后一站是如何在 C 中创建新的类型：**结构体**。`struct` 关键字允许你将一组不同的数据类型组合在一起，形成一个新的自定义数据类型：

```c
struct <struct_name> {
    <type> <member_name>;
    <type> <member_name>;
    ...
};
```

`Modules/arraymodule.c` 中的部分示例展示了一个 `struct` 声明：

```c
struct arraydescr {
    char typecode;
    int itemsize;
    ...
};
```

这将创建一个名为 `arraydescr` 的新数据类型，它有许多成员，其中前两个是`char typecode` 和 `int itemsize`。

结构体通常被用作 `typedef` 的一部分，其为结构体名提供了一个简单的别名。在上面的示例中，新类型中的所有变量都必须被一个全名进行声明：`struct arraydescr x;`。

你会经常看到这样的语法：

```c
typedef struct {
    PyObject_HEAD
    SEM_HANDLE handle;
    unsigned long last_tid;
    int count;
    int maxvalue;
    int kind;
    char *name;
} SemLockObject;
```

这将创建一个新的自定义结构类型，并为其命名为 `SemLockObject`。要声明此类型的变量，你可以简单地使用别名：`SemLockObject x;`。
