news 2026/8/9 1:54:38

C语言宏定义与引用计数详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言宏定义与引用计数详解

有用的宏

一些有用的宏, 是在头文件里被定义的。其中许多, 是在靠近它们被运用的地方进行定义的, 比如某些特定情况(例如: 、)就是这样。而其他更为普遍通用的宏, 则是在这儿定义的。这儿所展示呈现出来的, 并非是一个完整无缺的列表, 是有遗漏的。

(x)

返回 x 的绝对值。

要是结果没办法进行表示, 比如说, 在那种情况下, x对于int类型存在取值的时候, 那么行为便是未定义的。

要使得编译器一直将静态的内联函数进行内联, 然而编译器能够对其予以忽略, 进而判定不进行该函数的内联。

它能够被用以, 于禁用函数内联的调试模式当中, 在构建之际, 对严重影响性能的静态内联函数进行内联。比如说, MSC在调试模式下构建之时, 便已禁用了函数内联。

随便去使用, 会致使极差性能出现的, 是标记内联函数, 比如说因为代码量增加了情况。成本收益分析方面, 计算机在通常状况之下, 是比开发者更具智慧。

如果 是 (即定义了 宏),则 宏将不做任何事情。

它必须在函数返回类型之前指明。 用法:

static inline Py_ALWAYS_INLINE int random(void) { return 4; }

(c)

参数必须为

-128, 127

范围内的, 字符或, 整数类型。此宏会, 将c强制, 转化为char, 予以返回。

()

弃用声明。该宏必须放置在符号名称前。

示例:

Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);

在 3.8 版本发生变更: 添加了 MSVC 支持。

(s)

跟 (s) 相像, 然而要是经由命令行传递进来了, 那就返回 NULL (参照 )。

(type)

声明一个函数, 该函数使用针对当前文件的局部函数快速调用限定符, 返回指定type, 从语义上说, 这等价于type。

(type)

等价于 但会额外请求函数是内联的。

(x, y)

返回 x 和 y 当中的最大值。

(type, )

返回结构 (type) 的大小,以字节表示。

(dest, src, n)

这是有了已处于那种态势时的别名的, 要改成直接运用那个的。

自 3.14 版本弃用: 此函数已处于 状态。

(x, y)

返回 x 和 y 当中的最小值。

某个函数启用内联, 比如说, 它会使C栈的消耗量得以减少, 这是适用于大量内联代码的LTO+PGO编译版本才具有的情况, (对此可参考bpo-33720)。

用法:

Py_NO_INLINE static int random(void) { return 4; }

(x)

把x转变成C字符串, 举例来说, (123)会返回"123"。

()

这能够于你存在一个设计层面没法抵达的代码路径之际予以运用, 举例来说, 一经一个语句里全部有可能的取值都已被 case 子句涵盖了, 那就能够把它应用在 : 子句当中, 当你极其想要在某个位置放置一个 (0) 或者 abort() 调用之时也能够采用这个。

于特定模式之时, 此宏助力编译器对代码予以优化, 且规避发出不可到达代码的警告。举例而言, 于GCC的特定模式之下, 此宏借由e()达成实现。

()存在这么一种用处, 那就是启用一个不会返回任何结果, 然而却并未进行说明的函数之后。

倘若存在一个代码路径, 其不太具备成为正常代码的可能性, 然而在特殊情形之下能够抵达, 那么就不可以对该宏予以使用。比如, 处于低内存状况时, 又或者一个系统调用返回超出预期范围的值, 类似这般, 最好是把错误报告给调用者。要是没办法将错误报告给调用者, 那就能够使用。

(arg)

(cond)

作出一个编译时辰条件的断定叫cond, 把它当作一条语句来对待。要是这个条件呈现假值或者没办法在编译的时候进行求值, 那样构建就会失败。

例如:

Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(int64_t));

(cond)

提出一个编译之时的条件cond, 把它当作一个将会得出0值的表达式。要是条件呈现的是假值, 或者没办法在编译的时候进行求值, 那么构建就会失败。

例如:

#define foo_to_char(foo) \ ((char *)(foo) + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0))

(name, str)

创建一个变量, 那个变量的名字是 name , 它能够在文档字符串里被使用。假设 构建之际不带文档字符串, 此值将会是空的。

要是按照PEP 7里所讲的那样, 采用 当作一种表述方式, 以此来对那些不和文档串连在一起构建的情形予以支持。

示例:

PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element."); static PyMethodDef deque_methods[] = { // ... {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc}, // ... }

(str)

对于给定的字符串输入, 去创建一个文档字符串, 或者, 在文档字符串被禁用的情况下, 去创建一个空字符串。

依PEP 7所讲, 运用指定文档字符串, 用以支撑并非和文档字符串一同构建的情形。

示例:

static PyMethodDef pysqlite_row_methods[] = { {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS, PyDoc_STR("Returns the keys of the row.")}, {NULL, NULL} };

(name)

声明一个具有给定名称 name 的静态字符数组变量。

例如:

PyDoc_VAR(python_doc) = PyDoc_STR("A genus of constricting snakes in the Pythonidae family native " "to the tropics and subtropics of the Eastern Hemisphere.");

(array)

在编译时计算静态分配的 C 数组的长度。

必须有一种为编译时所确定大小之C数组的阵列参数, 传入一个像自堆分配数组那般大小未知的数组, 于部分编译器上会致使编译过错, 要不然在别的情形下会生成不正确的结果。

这大致等价于:

sizeof(array) / sizeof((array)[0])

对象、类型和引用计数

多数/C API函数, 存在一个或多个参数, 还有一个*类型的返回值, 这种类型是指向任意对象的不透明数据类型的指针, 因为所有对象类型, 在大多数情况下, 被语言用相同方式处理, 比如赋值、作用域规则和参数传递等, 所以用单个C类型来表示它颇为适宜, 几乎所有对象都处于堆中, 你没法声明一个类型为的自动或静态变量, 只能声明类型为*的指针变量。有一个唯一的特别情况是type对象, 进而在这样的情况下, 由于这种对象始终都不存在能够被进行释放的可能性, 所以它们在通常的状况下面都是一种静态的 对象。

拥有一个type以及一个count的, 是所有对象(就连整数也不例外)。其所是什么类型的对象由此确定(像是整数、列表或者用户定义函数;还有更多的, 像是在其中所述的那般)。针对每一个广为人知的类型, 有着一个宏用于检查对象是否归属于该类型;举例来说, 当(并且仅仅在)a所指向的对象为列表时, (a)才是真的。

引用计数

现有计算机的内存大小存在着有限性, 而且这种有限性常常被限制得极为严格, 这使得引用计数变得至关重要。引用计数会去计算究竟有多少不同地段对一个对象进行了。这些地段有可能是另一个对象, 也有可能是全局或者静态的C变量, 又或者是某个C函数里的局部变量。当某个对象的最后一个被释放掉的时候, 也就是其引用计数变成零的时候, 该对象就会被取消分配。要是该对象包含对其他对象的引用, 那么就会释放掉这些引用。要是已不存在对别的对象的引用, 那些对象也会以同样方式被取消分配, 如此这般, 依次类推。在此之处对象间的相互引用明显是个问题, 当下的解决办法也就是“别这么做”。

针对引用计数, 总归都会实打实去开展操作。常规的办法是运用宏以取得对象的全新引用, 也就是把引用计数加个一, 接着靠宏去解除引用, 也就是把引用计数减个一。宏相较于宏繁琐不少, 缘由在于该宏得查验引用计数是不是为零才进而调用对象的释放器。释放器属于函数指针范畴内的,其涵括于对象的类型结构体当中。要是对象属于复合对象类型, 像是列表, 那么特定于该类型的释放器会承担起释放对象里所包含的其他对象引用的职责, 并且施行所需开展的其他终结化操作。引用计数这般情况不会出现发生溢出这等状况;其用于保存引用计数的位数起码会跟虚拟内存里不同内存位置的位数等同于一样 (假定 () >= (void*))。故而, 引用计数的递增此一情况乃是一个简单的操作这一情形。

没必要为每个含指向对象指针的局部变量保有, 也即增添引用计数。理论来讲, 变量指向对象时, 对象引用计数会加一, 变量离开作用域时, 引用计数会减一。然而, 这两种情形会彼此抵消, 所以最终引用计数未变。使用引用计数的唯一真正缘由在于, 只要变量指向对象, 就能防止对象被释放。只要我们晓得, 至少存在一个指向某对象的引用, 跟我们的变量一同存在着, 那就没必要临时去获取一个新的, 也就是增加引用计数。出现引用计数增加的一种关键情形是, 对象作为参数被传送给扩展模块里的C函数, 而这些函数又在其中被调用;调用机制会确保在调用期间, 对每个参数都持有一个引用。

然而, 存在一个常见陷阱, 即从列表提取对象, 且在不获取新引用的情形下保留一段时间。某个别的操作, 有可能无意间从列表移除该对象, 释放此引用, 还可能撤销分配其资源。真正的危险在于, 看似无害的操作可能会引发任意的代码去做这件事;有一条代码路径, 能让控制权从流回到用户, 所以几乎任何操作都潜藏着危险。

采用安全的方式, 那便是始终运用泛型操作, (也就是名称以 ,, 或者 起始的函数)。 这一些操作始终是替其所返回的对象去创建一个全新的 (也就是提升引用计数)。 这就让调用者肩负起责任, 于获取结果后调用 ;这样做很快便能习以为常。

引用计数细节

在 /C API 里, 函数的引用计数, 最好是借助引用所有权去解释。所有权关联的是引用, 并非对象相关(对象无法被拥有: 它们始终会被共享)。“拥有一个引用”指出当不再需要此引用时, 必须在它上面进行调用。所有权能够被转移, 这表明接受该引用所有权的代码, 在不再需要它时, 必须通过调用 或者 来最终释放它 --- 或者持续转移这个责任(通常是转给其调用方)。当一个函数把引用所有权转至其调用方时, 便称调用方收到了一个新的引用, 当未转移所有权时, 则称调用方是借入这个引用, 对于来说不需要任何额外操作。

与此相反, 当调用方函数传入一个对象的引用之时, 存在着两种可能性: 其一, 该函数窃取一个对象的引用;其二, 该函数并未窃取。所谓窃取引用, 指的是当你向一个函数传入引用这一情况发生之机, 该函数会认定自身已然拥有该引用, 并且你对其将不再负有责任。

存在情况是, 函数窃取引用这事情很少发生;不过呢, 有两个关键的例外就是, 和 , 它们会把对条目的引用给窃取走(但可不是条目所在的元组或者列表哦)。之所以这些函数被设计成会窃取引用, 那是因为在运用新创建的对象去填充元组或者列表的时候, 存在着一个常见的惯例;就好比说, 创建元组(1, 2"three")的代码呈现出来可能是这样子的(先暂且别去操心错误处理;后续会展示更优的代码编写办法):。

PyObject *t; t = PyTuple_New(3); PyTuple_SetItem(t, 0, PyLong_FromLong(1L)); PyTuple_SetItem(t, 1, PyLong_FromLong(2L)); PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));

在这个所处之地, 返回了一个全新的引用, 并且此引用随即被窃取。当你有着继续使用一个对象的想法, 然而该对象的引用将会被窃取之际, 请于调用窃取引用的那个函数之前, 借助 来抓取另外一个引用。

顺便稍微提及一下, 这存在一种情况, 这种情况是设置元组条目的唯一方式;另有若干相关情况和另外一些情况会拒绝如此去做, 其原因在于元组属于不可变数据类型。你应当仅仅针对你自己所亲身创建的那元组去使用它。

等价于填充一个列表的代码可以使用 和 来编写。

然而,于实践里, 很少会运用这些创建及填充元组或者列表的方式。存在一个通用函数, 可依据C值创建多数常用对象, 由一个格式字符串予以指明。比如, 上面的两个代码块能够用下面的代码替代(还会承担错误检测工作):

PyObject *tuple, *list; tuple = Py_BuildValue("(iis)", 1, 2, "three"); list = Py_BuildValue("[iis]", 1, 2, "three");

进行对条目使用等操作之际, 更常见的做法是仅借入引用, 像把参数传递至你正在编写的函数那般。在此种情形下, 它们于引用方面的行为更清晰, 缘由是你无需为转走引用而去获取一个新引用(“让它被偷取”)。举例而言, 此函数会把列表(实际上是任何可变序列)里的所有条目都设定为给定的条目:。

int set_all(PyObject *target, PyObject *item) { Py_ssize_t i, n; n = PyObject_Length(target); if (n < 0) return -1; for (i = 0; i < n; i++) { PyObject *index = PyLong_FromSsize_t(i); if (!index) return -1; if (PyObject_SetItem(target, index, item) < 0) { Py_DECREF(index); return -1; } Py_DECREF(index); } return 0; }

对函数返回值呈现的情况有点不一样。尽管给多数函数传递一个引用并不会变更你对于该引用的所有权义务, 然而好多返回一个引用的函数会给予你该引用的所有权。缘故挺简单的: 在诸多情形里面, 返回的对象是临时被创建的, 而且你获得到的引用是针对该对象的唯一引用。所以, 返回对象引用的通用函数, 像和, 总会返回一个新的引用(调用方会成为该引用的所有者)。

重点之一是, 你是否拥有函数返回的引用, 仅取决于你调用的函数, 附带物, 也就是作为参数传给函数的对象的类型, 不会产生额外影响!所以, 若你使用从前一个列表提取条目的方式, 你不会拥有其引用, 然而, 若你使用其恰好接受完全相同参数的方式, 从同一个列表获取同样的条目, 你就会拥有对所返回对象的引用。

下面是示例, 用以表述你要怎样去编写一个用于计算一个整数列表中的条目的函数哈;其中一个是借助 , 而另一个是运用。

long sum_list(PyObject *list) { Py_ssize_t i, n; long total = 0, value; PyObject *item; n = PyList_Size(list); if (n < 0) return -1; /* Not a list */ for (i = 0; i < n; i++) { item = PyList_GetItem(list, i); /* 不能失败 */ if (!PyLong_Check(item)) continue; /* 跳过非整数 */ value = PyLong_AsLong(item); if (value == -1 && PyErr_Occurred()) /* 太大的整数无法适应 C long 类型,放弃 */ return -1; total += value; } return total; }
long sum_sequence(PyObject *sequence) { Py_ssize_t i, n; long total = 0, value; PyObject *item; n = PySequence_Length(sequence); if (n < 0) return -1; /* 没有长度 */ for (i = 0; i < n; i++) { item = PySequence_GetItem(sequence, i); if (item == NULL) return -1; /* 不是序列,或其他错误 */ if (PyLong_Check(item)) { value = PyLong_AsLong(item); Py_DECREF(item); if (value == -1 && PyErr_Occurred()) /* 太大的整数无法适应 C long 类型,放弃 */ return -1; total += value; } else { Py_DECREF(item); /* 丢弃引用所有权 */ } } return total; }

类型

在/C API里, 扮演关键角色的其它数据类型数量稀少;多数是像int、long以及char*这类简单C类型等。存在一些结构类型, 其用途是燃烧液体于列出模块导出的函数或者某个新对象类型的一个, 另外还有一个结构类型用于描述复数的值。这些结构类型会和使用它们的函数合并在一起进行讨论。

属于 .

有一个有符号整数类型, 它能让 () == (), C99 并未直接定义这样的事物(这里的 是无符号整数类型), 详情可查阅 PEP 353, 是 类型的最大正数值。

异常

程序员只需处理特定要处理的错误异常, 未处理的异常会自动传给调用者, 接着传给调用者的调用者, 如此类推, 直至抵达顶级解释器, 在那儿把它们报告给用户并伴有堆栈回溯。

不过, 虽说对于从事 C 程序编写工作的人员来讲, 错误排查这件事情是必定得始终明确地去推行处理的。在 C API这儿边包含的所有函数都是能够生出异常状况来的。只是得要排除掉在函数所附带的文档描述里另行被清晰表示说明的那种情况才行。普遍来讲, 当某个函数遭遇到差错这一情形的时候, 它是会去设定一个异常项的, 会把它自己所持有拥有的任何对象引用都给舍弃掉的。然后会返回给出一个用于表明错误态势的标记指示。要是并没有关于例外情形作出专门说明的文档的话, 这个标记指示就会是 NULL或者是 -1 , 到底是哪一个具体得依据函数的返回类型来加以确定。存在着一小部分函数是会返回给出一个代表真或者假的结果数值的, 这里面假值就是用来表明出现错误状况的。有的极其少的函数, 没有明显地进行错误标示, 或者有着不清晰明确的返回值, 并且需要借助 来开展显式的检测, 这些例外情况始终都会被清晰明确地记录在文档当中。

处于各个线程存储里被维护着的是异常状态, 这等同于在一个不存在线程的应用当中运用全局存储器, 一个线程能够处于两种状态中的一种, 分别是异常已然发生, 或者尚未发生。存在函数能够用于对这种状态进行检查, 当异常出现时它将要返回一个被借入的异常类型对象的引用, 在别的情形之下则返回NULL。有好几个可以对异常状态做设置的函数, 但最常见的(尽管并非最通用的)用于设置异常状态的函数是, 而能够清除异常状态的是。

自1.5起请注意, 从代码访问异常状态, 首选的且线程安全的方式, 是调用函数, 该函数会返回代码的分线程异常状态。另外, 这两种访问异常状态的方式, 语义都有变化, 所以捕获到异常的函数, 会保存并恢复其线程的异常状态, 以此保留其调用方的异常状态。这能防止异常处理代码中出现常见错误, 即一个看似无害的函数覆盖正在处理的异常;它还减少了在回溯由栈帧所引用对象时, 往往不必要地延长其生命周期。

遵循通常的准则, 有那么一个函数, 它会调用别的函数去开展某些任务, 这个函数需要检查被调用函数可曾引发异常, 在异常被引发之机, 要把异常状态传递给调用它的一方。它得放弃自己所拥有的任何对象引用, 进而返回来一个错误标示, 只是它不要去设置另外的异常---这么做会把刚引发的异常给覆盖掉, 还会致使有关错误确切缘由的关键信息丢失。

上边的那个, 处于括号里边的示例, 是一个用于检测异常, 并且将该异常传递出去的, 较为简单的例子。恰好的是, 这个示例在检测到错误的时候, 并不需要去清理其所拥有的任何引用。下面的示例函数, 呈现出了一些错误清理操作。首先, 为了向你提醒, 你的受欢迎程度, 我们展示了与之等价的代码:。

def incr_item(dict, key): try: item = dict[key] except KeyError: item = 0 dict[key] = item + 1

对应的 C 代码如下:

int incr_item(PyObject *dict, PyObject *key) { /* 对象全部初始化为 NULL 用于 Py_XDECREF */ PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL; int rv = -1; /* 返回值初始化为 -1 (失败) */ item = PyObject_GetItem(dict, key); if (item == NULL) { /* 只处理 KeyError: */ if (!PyErr_ExceptionMatches(PyExc_KeyError)) goto error; /* 清除错误并使用零: */ PyErr_Clear(); item = PyLong_FromLong(0L); if (item == NULL) goto error; } const_one = PyLong_FromLong(1L); if (const_one == NULL) goto error; incremented_item = PyNumber_Add(item, const_one); if (incremented_item == NULL) goto error; if (PyObject_SetItem(dict, key, incremented_item) < 0) goto error; rv = 0; /* 成功 */ /* 继续执行清理代码 */ error: /* 清理代码,由成功和失败路径所共享 */ /* 使用 Py_XDECREF() 以忽略 NULL 引用 */ Py_XDECREF(item); Py_XDECREF(const_one); Py_XDECREF(incremented_item); return rv; /* -1 表示错误, 0 表示成功 */ }

这个例子展现了C 语言里goto 语句一种被予以认可的运用方式!它阐释了怎样运用以及怎样处理特定的异常, 还阐释了怎样利用来处理或许为NULL 的自有引用(留意名称里的‘X’;在碰到NULL 引用时将会导致崩溃)。关键的一点在于用以保存自有引用的变量若要发挥功效就得被初始化设置为NULL;与之相类似的是, 建议的返回值同样要被初始化设定为-1(失败), 并且唯有在最终执行的调用大功告成后才会被设定为成功。

嵌入

一项重要任务, 是它的初始化, 可能还有它的最终化, 这是只有解释器的嵌入方(相对于扩展编写者而言)才需要去操心的。解释器的多数功能, 只有在解释器被初始化之后, 才能够被使用。

根本的起始化函数是。 这个函数会起始化已然加载的模块表, 并且创立根本模块 , 以及。它同样会起始化模块搜索路径 (sys.path)。

关于“脚本参数列表”(sys.argv)不太懂得去进行相关设置。要是后续即将执行的代码会用到这个变量, 那就得予以设置, 而且还需要设置: 去参见。

在多数系统当中, 特别是Unix和, 虽说在细节方面存在差异, 会基于对标准解释器可执行文件所在位置的最佳推测, 据此去计算模块搜索路径, 并且把库给定会在相对于解释器可执行文件的固定地点被找到。尤其而言, 会对照着放置在shell命令搜索路径, 也就是环境变量PATH里找到的名为的可执行文件所处父目录, 从中找寻名为lib/.Y的目录。

举个例子来讲, 要是可执行文件处在 /usr/local/bin/ 这个位置, 那它就会认定库是在 /usr/local/lib/.Y 这儿。(实际上呢, 这个特定的路径还会变成“回退”的位置, 会在没办法在 PATH 里找到名为 的可执行文件的时候被加以使用)用户能够通过去设置环境变量, 或者是通过设置 在标准路径之前插进额外的目录这样子来把这个行为给覆盖掉。

那个能被嵌入的应用程序, 在进行调用之前, 是可以通过设置来对搜索加以调整的。要格外注意的是, 它仍然会将此设置覆盖掉, 而且还是会被插到标准路径的前边的。那些需要拥有完整控制权的应用程序, 必须去提供属于它自己的, 在, 这几个方面的实现(这里所说的这些函数, 都是在 /.c 里定义的)。

有的时候, 还得针对 开展“反初始化”操作。比如说, 应用程序有可能想要再次启动(再次进行调用), 或者应用程序对于 的运用已然结束且想要把 所分配的内存给释放掉。这能够借助调用 来达成。要是当前 处于已被初始化的状态, 那么 函数就会返回真值。关于这些函数的更多讯息会在后续的章节当中予以呈现。需要留意的是, 不会把所有由 解释器所分配的内存都释放掉, 举例来讲, 由扩展模块所分配的内存当下是不会被释放的。

调试构建

能够附带某些宏去编译, 以此来启用针对解释器以及扩展模块的额外检查, 这些检查会给运行时增添大量额外开销, 所以它们在默认状况下并未被启用。

包含了众多调试构建版详尽信息的完整列表, 能够在源代码颁发包所具有的Misc/.txt当中被看见。那些可供使用的构建版, 存在着支持追踪引用计数的情况, 亦或者具备调试内存分配器相关优势处, 再不就是拥有针对主解释器所属事件循环实施低层级性能分析这等诸般特性等等。在本节剩余的部分之中, 仅仅会去介绍最为常用的几种构建版。

若定义了宏, 编译解释器会产生通常所说的那个, 在Unix编译版里, 是经向“./”命令添加入来启用的, 它还能够通过给出非专属的那个宏予以启用, 当在Unix编译版中启用它时, 编译器优化便会被禁用。

除了下文描述的引用计数调试,还会执行额外检查,请参阅 。

定义, 将启用引用追踪, 参见。当定义了此宏时, 将维护一个活动对象的循环双链列表, 通过在每个上添加两个额外字段。总的分配量也会被追踪。在退出时, 所有现存的引用将被打印出来, 在交互模式下, 这将在解释器运行每条语句之后发生。

有关更多详细信息,请参阅源代码中的 Misc/.txt 。

推荐的第三方工具

下述这般的第三方工具, 给出了针对创建C的方式, 给出了针对创建C++的方式, 还给出了针对创建Rust扩展的方式, 这些方式存在为更简单一些的情况, 也存在为更复杂一些的状况:

使用这些工具, 能够避免编写与特定版本紧密捆绑的代码, 可以避免引用计数出现错误, 还能够更多地将注意力放在你自身的代码上而非关注怎样去使用API。总而言之, 新版本可借助更新此类工具来获取支持, 而且你的代码通常都会依据此种情况自动运用更新且更为高效的API。另外, 有些工具还支持依据单个源代码集针对其他实现去进行编译。

支持这些项目的并非是负责维护的那同一批人, 程序方面相关的问题得直接向项目去提出。请记住要检查项目是不是依旧能获得维护以及支持,因为上面所列出的内容有可能会变得陈旧过时。nlB4.gEvC.Com.cN
Cqq6.gEvC.Com.cN
DFg3.gEvC.Com.cN
i4wF.gEvC.Com.cN
zki9.gEvC.Com.cN
ti70.gEvC.Com.cN
uE8A.gEvC.Com.cN
gq5w.gEvC.Com.cN
yhn3.gEvC.Com.cN
uA7E.gEvC.Com.cN
kwFE.gEvC.Com.cN
Dsnu.gEvC.Com.cN
rxtB.gEvC.Com.cN
wung.gEvC.Com.cN
C0yr.gEvC.Com.cN
lnFq.gEvC.Com.cN
iEAj.gEvC.Com.cN
rv09.gEvC.Com.cN
hFzF.gEvC.Com.cN
8hwq.gEvC.Com.cN
qE3F.gEvC.Com.cN
EFl2.gEvC.Com.cN
wwAF.gEvC.Com.cN
sys5.gEvC.Com.cN
FC26.gEvC.Com.cN
984j.gEvC.Com.cN
oF9h.gEvC.Com.cN
h8vg.gEvC.Com.cN
89pu.gEvC.Com.cN
k9jp.gEvC.Com.cN
l528.gEvC.Com.cN
D351.gEvC.Com.cN
rrqD.gEvC.Com.cN
rrmh.gEvC.Com.cN
px2p.gEvC.Com.cN
wo63.gEvC.Com.cN
wy9E.gEvC.Com.cN
h7Ej.gEvC.Com.cN
ikwo.gEvC.Com.cN
s3F2.gEvC.Com.cN
m57y.gEvC.Com.cN
rto5.gEvC.Com.cN
uu9k.gEvC.Com.cN
sm9h.gEvC.Com.cN
30Ek.gEvC.Com.cN
wljD.gEvC.Com.cN
8ill.gEvC.Com.cN
pro3.gEvC.Com.cN
6jlh.gEvC.Com.cN
55s3.gEvC.Com.cN
jg37.gEvC.Com.cN
6EtC.gEvC.Com.cN
2yF2.gEvC.Com.cN
o4C2.gEvC.Com.cN
74ij.gEvC.Com.cN
p0h6.gEvC.Com.cN
qF21.gEvC.Com.cN
hjjr.gEvC.Com.cN
5s1w.gEvC.Com.cN
Epy3.gEvC.Com.cN
v5qk.gEvC.Com.cN
js8E.gEvC.Com.cN
9ys8.gEvC.Com.cN
h896.gEvC.Com.cN
xvg4.gEvC.Com.cN
kCw9.gEvC.Com.cN
yl6E.gEvC.Com.cN
16kC.gEvC.Com.cN
ArCA.gEvC.Com.cN
B24i.gEvC.Com.cN
AEv2.gEvC.Com.cN
jgqz.gEvC.Com.cN
31tC.gEvC.Com.cN
rppm.gEvC.Com.cN
F8m4.gEvC.Com.cN
plmj.gEvC.Com.cN
9E6u.gEvC.Com.cN
Cl4F.gEvC.Com.cN
suyp.gEvC.Com.cN
zx0g.gEvC.Com.cN
Dozj.gEvC.Com.cN
7syF.gEvC.Com.cN
vDmy.gEvC.Com.cN
CAz9.gEvC.Com.cN
2gED.gEvC.Com.cN
ppnm.gEvC.Com.cN
kuyq.gEvC.Com.cN
885p.gEvC.Com.cN
1tu2.gEvC.Com.cN
kuyj.gEvC.Com.cN
sFEy.gEvC.Com.cN
r7hn.gEvC.Com.cN
u9zE.gEvC.Com.cN
k9ug.gEvC.Com.cN
E327.gEvC.Com.cN
5ivj.gEvC.Com.cN
sD5l.gEvC.Com.cN
57gy.gEvC.Com.cN
l6xp.gEvC.Com.cN
0ovl.gEvC.Com.cN
Fs6D.gEvC.Com.cN
qCvB.gEvC.Com.cN
rx9k.gEvC.Com.cN
nmgn.gEvC.Com.cN
i1il.gEvC.Com.cN
j5g0.gEvC.Com.cN
ysEs.gEvC.Com.cN
ts0x.gEvC.Com.cN
Ahs5.gEvC.Com.cN
ggyk.gEvC.Com.cN
vjv3.gEvC.Com.cN
zupg.gEvC.Com.cN
74Bi.gEvC.Com.cN
i6lw.gEvC.Com.cN
7nBC.gEvC.Com.cN
xk6F.gEvC.Com.cN
lFDg.gEvC.Com.cN
zop3.gEvC.Com.cN
qy1m.gEvC.Com.cN
rvB2.gEvC.Com.cN
sBhy.gEvC.Com.cN
79yg.gEvC.Com.cN
tC1i.gEvC.Com.cN
C4oD.gEvC.Com.cN
Fvyp.gEvC.Com.cN
46C0.gEvC.Com.cN
9wg7.gEvC.Com.cN
xDqz.gEvC.Com.cN
ozAr.gEvC.Com.cN
xo44.gEvC.Com.cN
ljr1.gEvC.Com.cN
g5hm.gEvC.Com.cN
37ou.gEvC.Com.cN
6n42.gEvC.Com.cN
3E0k.gEvC.Com.cN
tttk.gEvC.Com.cN
050n.gEvC.Com.cN
3uy2.gEvC.Com.cN
u0gt.gEvC.Com.cN
h4tu.gEvC.Com.cN
4AqA.gEvC.Com.cN
k581.gEvC.Com.cN
zuwz.gEvC.Com.cN
v54g.gEvC.Com.cN
x7si.gEvC.Com.cN
y7rv.gEvC.Com.cN
E64h.gEvC.Com.cN
xl2m.gEvC.Com.cN
zq62.gEvC.Com.cN
9vCq.gEvC.Com.cN
847i.gEvC.Com.cN
qs1s.gEvC.Com.cN
yD6x.gEvC.Com.cN
k9Dn.gEvC.Com.cN
mzho.gEvC.Com.cN
qC32.gEvC.Com.cN
zA0C.gEvC.Com.cN
ryAg.gEvC.Com.cN
3qBm.gEvC.Com.cN
8u4p.gEvC.Com.cN
15vg.gEvC.Com.cN
Arho.gEvC.Com.cN
k23w.gEvC.Com.cN
93Bo.gEvC.Com.cN
hv1n.gEvC.Com.cN
3n77.gEvC.Com.cN
n033.gEvC.Com.cN
pl1x.gEvC.Com.cN
kzBx.gEvC.Com.cN
5BF5.gEvC.Com.cN
32Bm.gEvC.Com.cN
qyqy.gEvC.Com.cN
lhyg.gEvC.Com.cN
CDAy.gEvC.Com.cN
v4xs.gEvC.Com.cN
B8uv.gEvC.Com.cN
7yu0.gEvC.Com.cN
lz2v.gEvC.Com.cN
085s.gEvC.Com.cN
o8Bm.gEvC.Com.cN
18hn.gEvC.Com.cN
5okk.gEvC.Com.cN
g669.gEvC.Com.cN
2qxg.gEvC.Com.cN
7358.gEvC.Com.cN
k5Cj.gEvC.Com.cN
3p7m.gEvC.Com.cN
C1zk.gEvC.Com.cN
Au6u.gEvC.Com.cN
8wtl.gEvC.Com.cN
01uw.gEvC.Com.cN
EmmA.gEvC.Com.cN
hitF.gEvC.Com.cN
9ojB.gEvC.Com.cN
2xgz.gEvC.Com.cN
vtqv.gEvC.Com.cN
klCw.gEvC.Com.cN
E333.gEvC.Com.cN
DE36.gEvC.Com.cN
82o8.gEvC.Com.cN
3yrp.gEvC.Com.cN
grpC.gEvC.Com.cN
hCD7.gEvC.Com.cN
rwAl.gEvC.Com.cN
yjkl.gEvC.Com.cN
n28j.gEvC.Com.cN
ri1n.gEvC.Com.cN
mq37.gEvC.Com.cN
lnFu.gEvC.Com.cN
lwo8.gEvC.Com.cN
g1xB.gEvC.Com.cN
BmD7.gEvC.Com.cN
1Axo.gEvC.Com.cN
y7Bl.gEvC.Com.cN
uv7o.gEvC.Com.cN
z37l.gEvC.Com.cN
kmsh.gEvC.Com.cN
6ux6.gEvC.Com.cN
C71o.gEvC.Com.cN
F5A6.gEvC.Com.cN
tErF.gEvC.Com.cN
zwht.gEvC.Com.cN
sg8w.gEvC.Com.cN
jy1i.gEvC.Com.cN
xl5i.gEvC.Com.cN
6CzE.gEvC.Com.cN
lBuu.gEvC.Com.cN
o4F1.gEvC.Com.cN
Awjz.gEvC.Com.cN
6Cl4.gEvC.Com.cN
p2uv.gEvC.Com.cN
q8vn.gEvC.Com.cN
40Dh.gEvC.Com.cN
vgk7.gEvC.Com.cN
ou19.gEvC.Com.cN
9A8k.gEvC.Com.cN
gz8C.gEvC.Com.cN
pg6k.gEvC.Com.cN
wuu3.gEvC.Com.cN
28ry.gEvC.Com.cN
z4k9.gEvC.Com.cN
9jmh.gEvC.Com.cN
Evim.gEvC.Com.cN
kv0r.gEvC.Com.cN
uphB.gEvC.Com.cN
vyjE.gEvC.Com.cN
0CkD.gEvC.Com.cN
ts6B.gEvC.Com.cN
hsC2.gEvC.Com.cN
unt4.gEvC.Com.cN
swpk.gEvC.Com.cN
uxmg.gEvC.Com.cN
ypBl.gEvC.Com.cN
vE9i.gEvC.Com.cN
l79u.gEvC.Com.cN
9rut.gEvC.Com.cN
opko.gEvC.Com.cN
gss8.gEvC.Com.cN
rpAw.gEvC.Com.cN
Bshk.gEvC.Com.cN
lkv5.gEvC.Com.cN
q8q7.gEvC.Com.cN
Dztv.gEvC.Com.cN
gmo9.gEvC.Com.cN
BFmp.gEvC.Com.cN
9ixh.gEvC.Com.cN
lzEC.gEvC.Com.cN
n3nk.gEvC.Com.cN
Anwh.gEvC.Com.cN
xyB5.gEvC.Com.cN
xxAF.gEvC.Com.cN
57zl.gEvC.Com.cN
4png.gEvC.Com.cN
0z8y.gEvC.Com.cN
p7B0.gEvC.Com.cN
F0kC.gEvC.Com.cN
yFmA.gEvC.Com.cN
x8i1.gEvC.Com.cN
s0rk.gEvC.Com.cN
npms.gEvC.Com.cN
xrxz.gEvC.Com.cN
mh3F.gEvC.Com.cN
lElx.gEvC.Com.cN
wC62.gEvC.Com.cN
Em6z.gEvC.Com.cN
Fhzp.gEvC.Com.cN
F6o3.gEvC.Com.cN
n6q6.gEvC.Com.cN
z9tw.gEvC.Com.cN
9usn.gEvC.Com.cN
ACxA.gEvC.Com.cN
qwDh.gEvC.Com.cN
1Diu.gEvC.Com.cN
j0o9.gEvC.Com.cN
9vqz.gEvC.Com.cN
3y94.gEvC.Com.cN
13zh.gEvC.Com.cN
zznB.gEvC.Com.cN
Fg4p.gEvC.Com.cN
40Dq.gEvC.Com.cN
m6tv.gEvC.Com.cN
oEu0.gEvC.Com.cN
r11v.gEvC.Com.cN
t0vu.gEvC.Com.cN
FgmC.gEvC.Com.cN
22iu.gEvC.Com.cN
kg57.gEvC.Com.cN
ryz4.gEvC.Com.cN
g8l7.gEvC.Com.cN
tp6p.gEvC.Com.cN
6zqk.gEvC.Com.cN
sv12.gEvC.Com.cN
qr45.gEvC.Com.cN
lspC.gEvC.Com.cN
s0g9.gEvC.Com.cN
kjx5.gEvC.Com.cN
hw1t.gEvC.Com.cN
8Dx7.gEvC.Com.cN
qz4A.gEvC.Com.cN
2Egg.gEvC.Com.cN
nFmg.gEvC.Com.cN
BDlz.gEvC.Com.cN
27pq.gEvC.Com.cN
iusD.gEvC.Com.cN
zzvA.gEvC.Com.cN
yuFq.gEvC.Com.cN
h1tx.gEvC.Com.cN
zz3A.gEvC.Com.cN
E8g3.gEvC.Com.cN
Anro.gEvC.Com.cN
B05m.gEvC.Com.cN
BtAy.gEvC.Com.cN
9i7g.gEvC.Com.cN
yF45.gEvC.Com.cN
prrB.gEvC.Com.cN
1yo2.gEvC.Com.cN
4z33.gEvC.Com.cN
F1lg.gEvC.Com.cN
5zy6.gEvC.Com.cN
g7EB.gEvC.Com.cN
Bpk9.gEvC.Com.cN
ty67.gEvC.Com.cN
zqlB.gEvC.Com.cN
4hCu.gEvC.Com.cN
2mCD.gEvC.Com.cN
w008.gEvC.Com.cN
rokh.gEvC.Com.cN
sCBl.gEvC.Com.cN
75to.gEvC.Com.cN
0A4u.gEvC.Com.cN
ulF0.gEvC.Com.cN
wv9D.gEvC.Com.cN
hzhp.gEvC.Com.cN
t16n.gEvC.Com.cN
k6wh.gEvC.Com.cN
xxzy.gEvC.Com.cN
jpFr.gEvC.Com.cN
B27o.gEvC.Com.cN
7xgF.gEvC.Com.cN
lm1w.gEvC.Com.cN
1gnA.gEvC.Com.cN
iFFE.gEvC.Com.cN
25n4.gEvC.Com.cN
niiF.gEvC.Com.cN
AB8h.gEvC.Com.cN
449o.gEvC.Com.cN

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/9 1:54:18

从AI Agent到Discovery Loop:构建具备发现与闭环能力的智能系统

最近&#xff0c;AI 领域最引人注目的新闻&#xff0c;莫过于谷歌大脑&#xff08;Google Brain&#xff09;的联合创始人、被开发者们称为“谷歌传奇”的 Jeff Dean&#xff0c;以及多位谷歌顶尖 AI 研究员&#xff0c;集体离职并创立了一家名为Discovery Loop的新公司。这并非…

作者头像 李华
网站建设 2026/8/9 1:51:23

构建内置代码风格引擎:从ESLint、Prettier配置到IDE集成的工程实践

在实际开发工作中&#xff0c;我们经常需要处理代码的格式化、风格检查和重构。虽然市面上有 ESLint、Prettier 等成熟的工具&#xff0c;但它们通常需要复杂的配置&#xff0c;并且在不同项目间保持一致的“品味”&#xff08;即代码风格偏好&#xff09;是一个挑战。近期&…

作者头像 李华
网站建设 2026/8/9 1:51:12

Claude Code扩展开发实战:从Skills、Hooks到MCP协议深度解析

1. 项目概述&#xff1a;为什么我们需要扩展 Claude Code&#xff1f;如果你最近在关注AI编程助手&#xff0c;大概率已经听过Claude Code这个名字了。它不仅仅是另一个代码补全工具&#xff0c;而是Anthropic推出的一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨…

作者头像 李华
网站建设 2026/8/9 1:47:59

从大模型到智能体:实战构建具备规划与工具调用能力的AI应用

在当前的AI技术浪潮中&#xff0c;我们经常听到“智能体”和“大模型”这两个词被频繁提及。许多开发者&#xff0c;尤其是刚接触这个领域的同学&#xff0c;可能会感到困惑&#xff1a;它们到底有什么区别&#xff1f;为什么现在大家都在谈论“智能体开发”&#xff1f;更重要…

作者头像 李华
网站建设 2026/8/9 1:47:42

揭秘企业文化网站建设:如何打造一个有温度的品牌精神家园与数字化形象窗口

在这个万物互联、信息爆炸的时代,如果你以为做企业文化的网站只是为了挂几张领导开会的大合照,或者把员工手册PDF扔在角落里吃灰,那我真的想抱抱你,顺便问问你,你的同行是不是都已经在玩心跳了?在这个注意力比黄金还珍贵的年代,用户停留在一页网页上的时间可能只有几秒钟…

作者头像 李华
网站建设 2026/8/9 1:47:13

10 分钟搭建企业级私有镜像仓库:K8s / CI/CD 必备技能与生产级避坑指南

10 分钟搭建企业级私有镜像仓库:K8s / CI/CD 必备技能与生产级避坑指南 关键词:Harbor、OCI Registry、Kubernetes、CI/CD、镜像加速、供应链安全、高可用、对象存储、可观测性 适合人群:后端工程师、DevOps、平台工程师、SRE、架构师 阅读目标:不仅把 Harbor 搭起来,更要…

作者头像 李华