如何正确使用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
相关产品推荐
相关产品推荐

