2

我在 Win32 上将 C++builder 用于 GUI 应用程序。Borland编译器优化很差,不知道怎么用SSE。使用 mingw gcc 4.7 编译时,我有一个快 5 倍的函数。我考虑让 gcc 生成汇编代码,然后在我的 C 函数中使用这个代码,因为 Borland 编译器允许内联汇编。

C 中的函数如下所示:

void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
{
double s = 77.777;
size_t m = mA[NT-3];
AV[2]=x[n-4]+m*s;
}

为了简化我的问题,我使功能代码非常简单。我的真实功能包含许多循环。

Borland C++ 编译器生成了这个汇编代码:

  ;
  ; void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
  ;
  @1:
push      ebp
mov       ebp,esp
add       esp,-16
push      ebx
 ;
 ;  {
 ;      double s = 77.777;
 ;
mov       dword ptr [ebp-8],1580547965
mov       dword ptr [ebp-4],1079210426
 ;
 ;      size_t m = mA[NT-3];
 ;
mov       edx,dword ptr [ebp+20]
mov       ecx,dword ptr [ebp+24]
mov       eax,dword ptr [edx+4*ecx-12]
 ;
 ;      AV[2]=x[n-4]+m*s;
 ;
 ?live16385@48: ; EAX = m
xor       edx,edx
mov       dword ptr [ebp-16],eax
mov       dword ptr [ebp-12],edx
fild      qword ptr [ebp-16]
mov       ecx,dword ptr [ebp+8]
mov       ebx,dword ptr [ebp+12]
mov       eax,dword ptr [ebp+16]
fmul      qword ptr [ebp-8]
fadd      qword ptr [ecx+8*ebx-32]
fstp      qword ptr [eax+16]
 ;
 ;  }
 ;
 ?live16385@64: ;
 @2:
pop       ebx
mov       esp,ebp
pop       ebp
ret

而 gcc 生成的汇编代码是:

 _Test_Fn:
mov edx, DWORD PTR [esp+20]
mov eax, DWORD PTR [esp+16]
mov eax, DWORD PTR [eax-12+edx*4]
mov edx, DWORD PTR [esp+8]
add eax, -2147483648
cvtsi2sd    xmm0, eax
mov eax, DWORD PTR [esp+4]
addsd   xmm0, QWORD PTR LC0
mulsd   xmm0, QWORD PTR LC1
addsd   xmm0, QWORD PTR [eax-32+edx*8]
mov eax, DWORD PTR [esp+12]
movsd   QWORD PTR [eax+16], xmm0
ret
 LC0:
   .long    0
   .long    1105199104
   .align 8
 LC1:
   .long    1580547965
   .long    1079210426
   .align 8

我想获得有关如何在 gcc 和 Borland C++ 中完成函数参数访问的帮助。我在 Borland 的 C++ 中的函数类似于:

 void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
 {
__asm
  {
  put gcc generated assembler here
  }
 }

Borland 开始使用ebp寄存器,而 gcc 使用esp寄存器。我可以强制其中一个编译器使用 cdecl ou stdcall 之类的调用约定生成兼容的代码以访问参数吗?

4

3 回答 3

2

在这两种情况下,参数的传递方式类似。不同之处在于,Borland 生成的代码表达了相对于 EBP 寄存器的参数位置和相对于 ESP 的 GCC,但它们都引用了相同的地址。

Borlands 将 EBP 设置为指向函数堆栈帧的开头并表示相对于它的位置,而 GCC 不设置新的堆栈帧但表示相对于 ESP 的位置,调用者已将其指向调用者的末尾堆栈帧。

Borland 生成的代码在函数的开头设置了一个栈帧,导致 Borland 代码中的 EBP 等于 GCC 代码中的 ESP 减少了 4。这可以通过查看前两行 Borland 行看出:

push      ebp     ; decrease esp by 4
mov       ebp,esp ; ebp = the original esp decreased by 4

GCC 代码不会改变 ESP 并且 Borland 代码不会改变 EBP 直到过程结束,所以当访问参数时关系 IP 保持不变。

在这两种情况下,调用约定似乎都是cdecl,并且函数的调用方式没有区别。您可以在两者中添加关键字__cdecl以使其清楚。

 void __cdecl Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)

然而,将 GCC 编译的内联汇编添加到使用 Borland 编译的函数中并不简单,因为即使函数体只包含内联汇编,Borland 也可能会设置堆栈帧,导致 ESP 寄存器的值与 GCC 中使用的不同代码。我看到了三种可能的解决方法:

  1. 用 Borland 编译,不带“标准堆栈帧”选项。如果编译器发现不需要堆栈帧,这可能会起作用。
  2. 使用不带选项-fomit-frame-pointer的 GCC 进行编译。这应该确保至少 EBP 的值在两者中是相同的。该选项在 -O、-O2、-O3 和 -Os 级别启用。
  3. 手动编辑 GCC 生成的程序集,将对 ESP 的引用更改为 EBP 并将偏移量添加 4。
于 2013-04-08T07:14:55.117 回答
1

我建议您阅读一下应用程序二进制接口。这是一个相关链接,可帮助您找出什么编译器生成什么样的代码: https ://en.wikipedia.org/wiki/X86_calling_conventions

于 2013-04-07T23:05:52.903 回答
1

我会尝试使用 GCC 编译所有内容,或者查看是否仅使用 GCC 编译关键文件,其余的使用 Borland 并链接在一起是否有效。您解释的内容可以发挥作用,但这将是一项艰巨的工作,可能不值得您投入时间(除非它会在很多很多机器上非常频繁地运行)。

于 2013-04-07T23:53:27.817 回答