You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确使用ctypes.get_errno()?为何返回错误的errno值?

ctypes.get_errno() 返回错误0值的原因及可靠性分析

我在使用ctypes测试某个二进制库时,部分测试需要校验errno错误码,但调用ctypes.get_errno()却意外得到代表“成功”的0值,与预期不符。想知道这是什么原因导致的,以及ctypes.get_errno()是否可靠。

测试代码(test.py)

#!/usr/bin/env python3

import os
import ctypes
import errno

libc = ctypes.cdll.LoadLibrary("libc.so.6")
libc.write.restype = ctypes.c_ssize_t
libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t

TMP_FILE = "/tmp/foo"

def main():
    fd: int
    errno: int = 0

    fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT)
    if fd == -1:
        errno = ctypes.get_errno()
        print(strerror(errno))

    if (not errno and libc.write(fd, "foo", 3) == -1):
        errno = ctypes.get_errno()
        print(f"ERRNO: {errno}")
        print(os.strerror(errno))

    os.close(fd);
    os.remove(TMP_FILE)

    if errno:
        raise OSError(errno, os.strerror(errno))


if __name__ == "__main__":
    main()

测试输出

$ ./test.py
ERRNO: 0
Success

注意:我已经从其他帖子找到替代方案(见下方代码),但想了解ctypes.get_errno()的问题所在。

替代方案代码(test_with_workaround.py)

#!/usr/bin/env python3

import os
import ctypes

libc = ctypes.cdll.LoadLibrary("libc.so.6")
libc.write.restype = ctypes.c_ssize_t
libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t

TMP_FILE = "/tmp/foo"

_get_errno_loc = libc.__errno_location
_get_errno_loc.restype = ctypes.POINTER(ctypes.c_int)

def get_errno() -> int:
    return _get_errno_loc()[0]

def main():
    fd: int
    errno: int = 0

    fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT)
    if fd == -1:
        errno = get_errno()
        print(strerror(errno))

    if (not errno and libc.write(fd, "foo", 3) == -1):
        errno = get_errno()
        print(f"ERRNO: {errno}")
        print(os.strerror(errno))

    os.close(fd);
    os.remove(TMP_FILE)

    if errno:
        raise OSError(errno, os.strerror(errno))


if __name__ == "__main__":
    main()

替代方案输出

$ ./test_with_workaround.py
ERRNO: 9
Bad file descriptor
Traceback (most recent call last):
  File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 41, in <module>
    main()
  File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 37, in main
    raise OSError(errno, os.strerror(errno))
OSError: [Errno 9] Bad file descriptor

问题原因

  • ctypes.get_errno()的缓存机制:Python的ctypes.get_errno()并非直接读取系统原生的errno变量,而是维护了一个Python进程内的全局缓存副本。只有当通过ctypes调用的C函数被标记为use_errno=True时,ctypes才会在函数调用结束后自动将系统errno同步到这个缓存中。
  • 未配置use_errno导致同步失效:你的代码中调用libc.write时,没有为该函数设置use_errno=True。因此当write调用失败并设置了系统errno后,ctypes并未将这个值同步到缓存副本,导致ctypes.get_errno()返回的是之前的缓存值(此处为初始的0)。

ctypes.get_errno()的可靠性

它的可靠性完全取决于你是否正确配置了C函数的use_errno属性:

  • 如果为目标C函数设置了use_errno=True(或通过errcheck回调处理错误),ctypes.get_errno()就能正确获取到函数调用后的errno值,此时是可靠的。
  • 如果未配置,它只会返回历史缓存值,无法反映当前函数调用的真实错误状态,完全不可靠。

修复验证

修改test.py,为libc.write添加use_errno=True配置:

libc.write.restype = ctypes.c_ssize_t
libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t
libc.write.use_errno = True  # 新增此行

重新运行后,就能正确获取到errno=9(坏文件描述符),结果与替代方案一致。

替代方案有效的原因

你的替代方案直接调用了libc.__errno_location(),该函数返回系统原生errno变量的内存地址,读取该地址的值就是当前真实的errno状态,绕过了ctypes的缓存机制,因此能拿到正确的错误码。


内容的提问来源于stack exchange,提问作者vmonteco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:48:15