> For the complete documentation index, see [llms.txt](https://hai-shi.gitbook.io/cpython-internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hai-shi.gitbook.io/cpython-internals/9-evalutaion-loop/9.4-value-stack.md).

# 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 操作](/files/J12DRbE96NF0m69VUrNR)

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

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

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

![图9.4.2 值栈的 pop 操作](/files/PsfHWF6vvUqz3g9QqNYE)

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

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

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

![图9.4.3 向值栈添加两个元素](/files/eJ2z5IOJh48xdyVaY1XK)

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

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

![图9.4.4 从值栈取出元素](/files/tqrAzOzyVjEjIlWmt45Z)

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

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

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

![图9.4.5 值栈的 peek 操作](/files/7ukltKwcFl48hGnJh3tF)

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

```c
    DUP_TOP();
```

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

![图9.4.6 克隆值栈的顶部元素](/files/gstxBDWLA7rveJjT4nha)

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

```c
    ROT_TWO();
```

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

![图9.4.7 值栈的元素交换](/files/hz8sQTZ9IzEffYER0yDJ)

## Stack Effects

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

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