我有一些简单的代码将两个size_t
加在一起:
#include <stdlib.h>
extern "C" __declspec(dllexport) size_t _cdecl add(size_t x, size_t y)
{
return x + y;
}
(注意:此代码已编译并在64位系统上运行。)
通过Python的ctypes
调用该函数并将其传递为c_uint
类型的参数(大小为32位而不是64位)时,该函数将按预期工作:
import ctypes
lib = ctypes.cdll['./ctypetest.dll']
add = lib.add
add.restype = ctypes.c_uint
add.argtypes = [ctypes.c_uint, ctypes.c_uint]
add(1, 2) # = 3
作为健全性检查,我验证了uint
和size_t
的大小是否不同:
>>> ctypes.sizeof(ctypes.c_size_t)
8
>>> ctypes.sizeof(ctypes.c_uint)
4
在给定不同大小的参数的情况下,ctypes
如何成功调用此函数?
答案 0 :(得分:1)
答案取决于用于编译Python的C编译器的ABI调用约定。
听起来好像您在x86-64 Windows上。 1 如果是,则您的系统是基于Microsoft x64 ABI构建的。如果不是,那仍然是一个很好的例子,所以让我们假装你是。稍为简化, 2 该ABI的调用约定如下所示:
因此,您的c_uint
参数分别存储在RCX和RDX的低32位中,而每个寄存器的高32位被清除为0。
add
函数将RCX和RDX添加为无符号64位整数,其结果恰好是您期望的结果;一切正常。 3
但是,假设您使用的是不同的平台,并且使用了不同的ABI。实际上,您的想象力不必走得太远。如果在同一Windows计算机上运行32位程序,则将获得Microsoft IA-32 ABI而不是Microsoft x64。该ABI具有三种不同的调用约定,并且您声明中的_cdecl
现在选择这三种之一,
好的,现在c_uint
和size_t
都是32位,但是我们用c_ushort
做同样的事情。
您的Python代码将两个16位值压入堆栈。
add
尝试将两个值(如x | (y<<32)
中一样)用作其x
参数,然后将堆栈上与其相邻的所有内容用作其{{ 1}}参数。所以,您得到的是垃圾。
它甚至会变得更糟。
如果您使用过y
,该怎么办?在不执行任何操作的Microsoft x64 ABI中,但在Windows IA-32 ABI中,它指定与_stdcall
相同的参数传递顺序,但由被调用方而不是调用方进行堆栈清除。
因此,_cdecl
为您生成垃圾后,便清理了堆栈,并期望它的大小不同于您提供的大小,并且……实际上,我认为在这种情况下,您会得到删除它是因为堆栈的参数区域对齐到16个字节的页面,所以清除16个字节而不是8个字节并不重要。但这只是愚蠢的运气。
也有一些平台在 partial 寄存器中传递值。例如,add
的Win32s版本IIRC就是这样的:
AL只是AX的下半部分。并且将一个字节加载到AL中不会清除高半部分。因此,如果调用一个_fastcall
函数想添加两个16位数字,但又想添加两个8位数字又会怎样呢?您将得到_fastcall
,x
,y
和z*256
的总和,其中w*256
和z
就是剩下的东西在w
和AH
中通过先前的说明操作。
我所有奇怪的示例都来自32位或更小的ABI是有原因的。大多数64位ABI的设计都是较新的,并且不那么随意,并且专门用于使POSIX / C代码和/或Win64 / C代码良好运行,因此它们往往非常相似。例如,System V AMD64 ABI(x86_64上除Windows以外几乎用于所有其他功能),AArch64 ABI(ARM64上几乎用于所有功能)和PowerPC64(PowerPC64上所有功能使用)都具有与Microsoft x64 ABI,除了一组不同的整数参数寄存器和稍微不同的浮点数和填充规则外。但这并不意味着您可以依靠它来安全地获取错误的参数。这只是意味着您很难找到测试系统来检测和调试错误……
1。您没有说,但是DH
和__declspec
通常只出现在Windows代码中。而且您说的是“ 64位系统”,我怀疑您使用的是Itanium还是其他64位平台。
2。浮点数,SSE向量,大于64位的结构,varargs都有一些额外的复杂性。
3。您可能会对_cdecl
是0xffffffff + 0xffffffff
而不是0x00000001fffffffe
感到有些惊讶……但是由于您也误解了0xfffffffe
,因此将其截断为32位(而且您使用的是Little-endian系统,并且该系统返回寄存器中的值-如果两个都不正确,则答案为1。),由于这些都是无符号的int,因此会被截断并翻转看起来相同,因此两个错误被抵消,您会看到预期的restype
。