为何基于MMX SIMD的C数组乘法函数返回补码形式的int值?
问题根源分析
你的问题本质是数据类型不匹配加上MMX指令的特性导致的符号扩展错误,具体拆解如下:
1. 函数设计目标与你的使用方式不匹配
mmx_mul函数是为**16位整数数组(int16_t)**设计的,但你传递的是32位int数组:
- 函数内部用
movq加载64位数据(即4个16位元素),用pmullw指令对每对16位带符号整数执行乘法,结果保留低16位补码。 - 当你把32位
int数组传入时,每个32位int会被拆成两个16位元素(小端字节序下,低字节在前)。比如你的A[0] = -1(32位补码0xFFFFFFFF)会被拆成两个16位的0xFFFF(即-1),B[0] = -1同理。 - 执行
pmullw后,每对16位的-1 * -1得到1(16位补码0x0001),函数会把4个这样的16位结果打包成64位,然后写入内存——对应两个32位int:0x00010001(即65537),这就是你看到的第一个异常值。
2. 16位补码到32位int的符号扩展缺失
当函数把16位乘法结果写入内存后,你用32位int读取时,默认会把16位值当成无符号数扩展到32位(高16位补0):
- 比如16位的
-4补码是0xFFFC,当作为32位int读取时,会变成0x0000FFFC(即65532),而不是正确的带符号扩展0xFFFFFFFC(即-4)。 - 只有当结果是正数时,16位补码和32位补码的低16位一致,所以你看到最后一个值
7是正确的。
3. 额外的最高位1来自哪里?
你提到两个负数相乘时结果多一位最高位1,比如65537(0x10001),这其实是两个16位乘法结果被打包进了同一个32位int:这个int的低16位是第一个乘法结果1,高16位是第二个乘法结果1,合起来就是0x00010001,看起来像多了一位,但实际是两个独立的16位结果拼在了一起。
解决方案
方案1:使用正确的16位整数数组
把你的数组类型改成int16_t(需要包含<stdint.h>),并调整调用逻辑:
#include <stdint.h> int16_t A[8] = {-1, 4, 1, -1, 1, -2, -3, 7}; int16_t B[8] = {-1, -1, -1, -1, -1, -1, -1, 1}; int16_t C[8]; // 总元素数是8,不是16!因为每个元素是16位,8个元素就是8个16位值 mmx_mul(A, B, C, 8);
之后如果需要把int16_t转成int,编译器会自动处理16位到32位的带符号扩展:
int result = (int)C[0];
方案2:修复结果读取时的符号扩展
如果不能修改数组类型,在读取C数组时,手动将低16位的16位补码符号扩展到32位:
// 对每个C[i],提取低16位并符号扩展 int corrected = (int)(int16_t)(C[i] & 0xFFFF);
比如C[1] = 65532(0x0000FFFC),C[i] & 0xFFFF得到0xFFFC,转成int16_t就是-4,再转成int就是正确的-4。
方案3:修改函数适配32位int数组
如果你坚持用32位int数组,需要调整函数逻辑:
- 改用支持32位带符号乘法的SIMD指令(比如SSE2的
pmulld),因为MMX没有原生的32位带符号乘法指令。 - 调整循环计数和内存操作步长(每个32位int占4字节,而非2字节)。
不过从你的需求来看,方案1是最贴合原函数设计的最优解。
额外注意点
- 函数末尾的
EMMS指令是正确的,用来清空MMX状态,避免和x87浮点指令冲突。 - L3循环里的代码有小问题:
mov eax, [edi]读取的是4字节,但add edi,2,应该改成mov ax, [edi]来读取16位元素,避免读取多余的字节:mov ax, [edi]; 读取16位元素 imul ax, [esi]; 16位乘法 mov [ebx], ax; 存储16位结果
内容的提问来源于stack exchange,提问作者Jake
相关产品推荐
相关产品推荐

