# 9.4 值栈

## The Value Stack

在核心的求值循环中会创建一个值栈。这个栈包含了一系列指向 `PyObject` 实例的指针，这些实例可以是变量、函数的引用（在 Python 中也是对象）或其他类型的 Python 对象。

求值循环中的字节码指令将从值栈中获取输入。

## 字节码操作的例子：BINARY\_OR

在前几章我们已经探究了如何将二元操作编译成一条指令。如果你在 Python 使用了 `or` ：

```python
if left or right:
    pass
```

编译器会把操作符 `or` 编译成 `BINARY_OR` 指令：

```c
static int
binop(struct compiler *c, operator_ty op)
{
    switch (op) {
        case Add:
            return BINARY_ADD;
        ...
        case BitOr:
            return BINARY_OR;
        ...
```

在求值循环中，`BINARY_OR` 将从值栈中获取两个值作为左右操作数（`left` 和 `right`），随后以这两个对象作为参数调用函数 `PyNumber_Or()` ：

```c
    ...
    case TARGET(BINARY_OR): {
          PyObject *right = POP();
          PyObject *left = TOP();
          PyObject *res = PyNumber_Or(left, right);
          Py_DECREF(left);
          Py_DECREF(right);
          SET_TOP(res);
          if (res == NULL)
                goto error;
          DISPATCH();
    }
```

最后，将求得的结果 `res` 放在栈的顶部，覆写了当前栈顶的值。

## 模拟值栈

要理解求值循环，那你必须先理解值栈的工作原理。

一种理解是值栈就就像一个木钉，你可以在上面不断的摆放圆柱体。在这种情况下，你一次只能添加或删除一个圆柱体，并且总是添加到堆栈顶部或从堆栈顶部移除。在 CPython 中，你可以使用 `PUSH(a)` 宏将对象添加到值栈中，这里的 `a` 是一个指向 `PyObject` 对象的指针。

假设你创建了一个 `PyLong` 类型且值为 10 的对象，同时你想把它放入值栈中，可以参考下面的例子：

```c
    PyObject *a = PyLong_FromLong(10);
    PUSH(a);
```

这个操作将产生以下效果：

![图9.4.1 值栈的 push 操作](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-40aa248b0fe6a809b0c38397d5a2659122f91723%2F%E5%9B%BE9.4.1%20%E5%80%BC%E6%A0%88%E7%9A%84push.png?alt=media)

为了获取这个值，你可以在下一次操作中使用 `POP()` 宏来拿到栈顶的对象。

```c
    PyObject *a = POP(); // a is PyLongObject with a value of 10
```

这个操作将返回栈顶的值，并且操作完成后值栈中不存在任何值：

![图9.4.2 值栈的 pop 操作](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-7acf0a0e797042444e734ace749cb23bba7452a0%2F%E5%9B%BE9.4.2%20%E5%80%BC%E6%A0%88%E7%9A%84pop.png?alt=media)

现在假设你添加了两个值到值栈中：

```c
    PyObject *a = PyLong_FromLong(10);
    PyObject *b = PyLong_FromLong(20);
    PUSH(a);
    PUSH(b);
```

值栈内值的顺序与添加的顺序有关，所以 `a` 被添加到了值栈的第二个位置：

![图9.4.3 向值栈添加两个元素](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-44d66e3748114bc2e5a7c708b46ffcd9fb01453d%2F%E5%9B%BE9.4.3%20%E5%90%91%E5%80%BC%E6%A0%88%E6%B7%BB%E5%8A%A0%E4%B8%A4%E4%B8%AA%E5%85%83%E7%B4%A0.png?alt=media)

如果你要去获取一个栈顶的值，你将得到指向 `b` 的指针：

```c
    PyObject *val = POP(); // returns ptr to b
```

![图9.4.4 从值栈取出元素](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-a1574e7c14c297c72c1144cbdc3605c1d428d157%2F%E5%9B%BE9.4.4%20%E4%BB%8E%E5%80%BC%E6%A0%88%E5%8F%96%E5%87%BA%E5%85%83%E7%B4%A0.png?alt=media)

如果你想要在不弹出对象的情况下获取指向堆栈中顶部值的指针，那你可以使用 `PEEK(v)` 操作，其中 `v` 是栈中元素的位置：

```c
    PyObject *first = PEEK(0);
```

0 代表栈顶的位置，1 则代表栈中第二个位置：

![图9.4.5 值栈的 peek 操作](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-4b076c2d9d8b758da0b830dd44bcdd8cc779e230%2F%E5%9B%BE9.4.5%20%E5%80%BC%E6%A0%88%E7%9A%84peek%E6%93%8D%E4%BD%9C.png?alt=media)

`DUP_TOP()` 这个宏可以用于克隆栈顶部的值：

```c
    DUP_TOP();
```

这个操作将复制栈顶部的值，形成指向同一对象的两个指针：

![图9.4.6 克隆值栈的顶部元素](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-513b3af6d7aaca092490487fa90fca33ecf25b08%2F%E5%9B%BE9.4.6%20%E5%85%8B%E9%9A%86%E5%80%BC%E6%A0%88%E7%9A%84%E5%85%83%E7%B4%A0.png?alt=media)

`ROT_TWO()` 则可以交换栈中第一个和第二个值：

```c
    ROT_TWO();
```

此操作将切换第一个值和第二个值的顺序：

![图9.4.7 值栈的元素交换](https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-53102bf2e244db1e5df9aee1833ae043b14d67e4%2F%E5%9B%BE9.4.7%20%E5%80%BC%E6%A0%88%E7%9A%84%E5%85%83%E7%B4%A0%E4%BA%A4%E6%8D%A2.png?alt=media)

## Stack Effects

每一个操作码都有预定义的**栈操作**，可以由 `Python/compile.c` 中的函数 `stack_effect()` 计算得到。这个函数会返回操作码执行后的值栈中元素数目的增量。

这个增量可能是正值、负值或 0。在执行操作码时，若 `stack_effect()` 返回值（例如 `+1`）与值栈中的增量不匹配，就会抛出一个异常。
