当我将复杂的浮点数(complex.h)从 C++ 调用程序传递到 C 库时,在 32 位 Power PC 上运行时,该值无法正确传递。当我检测到这个问题时,我正在使用两个不同的开源软件库。我将其隔离到 C++ 将复杂值类型传递给纯 C 类型函数时的边界。我写了一些简单的代码来演示它。
#ifndef MMYLIB_3A8726C1_H
#define MMYLIB_3A8726C1_H
typedef struct aComplexStructure {
float r;
float i;
} myComplex_t;
#ifdef __cplusplus
#include <complex>
extern "C" {
void procWithComplex(float a, std::complex<float> *pb, std::complex<float> c, float d);
void procWithStruct(float a, myComplex_t *pb, myComplex_t c, float d);
}
#else /* __cplusplus */
#include <complex.h>
void procWithComplex(float a, float complex *pb, float complex c, float d);
void procWithStruct(float a, myComplex_t *pb, myComplex_t c, float d);
#endif
#endif /* MYLIB_3A8726C1_H */
C源文件如下
#include <stdio.h>
#include "myLib.h"
void procWithComplex(float a, complex float * pb, complex float c, float d)
{
printf("a=%f\n", a);
printf("b=%f + %fi\n", creal(*pb), cimag(*pb));
printf("c=%f + %fi\n", creal(c), cimag(c));
printf("d=%f\n", d);
}
void procWithStruct(float a, myComplex_t* pb, myComplex_t c, float d)
{
printf("a=%f\n", a);
printf("b=%f + %fi\n", pb->r, pb->i);
printf("c=%f + %fi\n", c.r, c.i);
printf("d=%f\n", d);
}
调用C++程序如下
#include <iostream>
#include "myLib.h"
int main()
{
float a = 1.2;
std::complex<float> b = 3.4 + 3.4I;
std::complex<float> c = 5.6 + 5.6I;
float d = 9.876;
myComplex_t b_s, c_s;
b_s.r = b.real();
b_s.i = b.imag();
c_s.r = c.real();
c_s.i = c.imag();
std::cout << "a=" << a << std::endl;
std::cout << "b=" << b << std::endl;
std::cout << "c=" << c << std::endl;
std::cout << "d=" << d << std::endl << std::endl;
// c is a 64 bit structure being passed by value.
// on my 32 bit embedded powerpc platform, it is being
// passed by reference, but the underlying C library is
// reading it by value.
procWithComplex(a, &b, c, d);
std::cout << std::endl;
// This is only here to demonstrate that a 64 bit value field
// does pass through the C++ to C boundry
procWithStruct(a, &b_s, c_s, d);
return 0;
}
通常我期望输出是
a=1.2
b=(3.4,3.4)
c=(5.6,5.6)
d=9.876
a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000
a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000
但是当我在嵌入式电源 PC 机上运行源代码时,我得到的输出显示复杂的值类型未正确传递。
a=1.2
b=(3.4,3.4)
c=(5.6,5.6)
d=9.876
a=1.200000
b=3.400000 + 3.400000i
c=-0.000000 + 9.876000i
d=0.000000
a=1.200000
b=3.400000 + 3.400000i
c=5.600000 + 5.600000i
d=9.876000
我从 gdb 检查了参数的大小,从调用和函数框架中检查了浮点、复数浮点指针、复数浮点和浮点的大小为 4 字节、4 字节、8 字节和 4 字节。
我意识到我可以在跨越 c++ 到 c 边界时将复数值参数更改为指针或我自己的结构,但我想知道为什么我不能在 power pc 上将复数值类型从 c++ 传递到 c 。
我创建了另一个示例,只是这次我转储了一些程序集以及寄存器值。
int x = 22;
std::complex<float> y = 55 + 88I;
int z = 77;
void simpleProc(int x, complex float y, int z)
在调用之前传入参数。
x = 22
y = {_M_value = 55 + 88 * I}
Looking at raw data *(int*)&y = 1113325568
z = 77
这应该是汇编代码,其中保存返回地址并保存要传递到例程中的参数。
x0x10000b78 <main()+824> lwz r9,40(r31)
x0x10000b7c <main()+828> stw r9,72(r31)
x0x10000b80 <main()+832> lwz r9,44(r31)
x0x10000b84 <main()+836> stw r9,76(r31)
x0x10000b88 <main()+840> addi r9,r31,72
x0x10000b8c <main()+844> lwz r3,16(r31)
x0x10000b90 <main()+848> mr r4,r9
x0x10000b94 <main()+852> lwz r5,20(r31)
x0x10000b98 <main()+856> bl 0x10000f88 <simpleProc>
查看分支后的组件:)
x0x10000f88 <simpleProc> stwu r1,-48(r1)
x0x10000f8c <simpleProc+4> mflr r0
x0x10000f90 <simpleProc+8> stw r0,52(r1)
x0x10000f94 <simpleProc+12> stw r29,36(r1)
x0x10000f98 <simpleProc+16> stw r30,40(r1)
x0x10000f9c <simpleProc+20> stw r31,44(r1)
x0x10000fa0 <simpleProc+24> mr r31,r1
x0x10000fa4 <simpleProc+28> stw r3,8(r31)
x0x10000fa8 <simpleProc+32> stw r5,12(r31)
x0x10000fac <simpleProc+36> stw r6,16(r31)
x0x10000fb0 <simpleProc+40> stw r7,20(r31)
x0x10000fb4 <simpleProc+44> lis r9,4096
这些是我们完全进入例程后的值(在分配变量值之后。
x = 22
y = 1.07899982e-43 + 0 * I
z = 265134296
$r3 = 22
$r4 = 0x9ffff938
*(int*)$r4 = 1113325568
$r5 = 77
*(int*)(&y) = 77
我的外行人的观点是,C++ 似乎将复杂值类型作为引用或指针类型传递?但 C 是否将其视为值类型?那么这是 power pc 上 gcc 的问题吗?我使用的是gcc4.7.1。我正在另一台机器上构建 gcc4.9.3 作为交叉编译器。一旦我从较新的编译器获得工作的输出,我就会以任何方式更新这篇文章。
交叉编译器工作时遇到问题,但查看原始问题的内存转储,它确实表明在 Power PC 平台上,复杂值不是按值传递的。我将结构体的示例放在这里是为了表明 64 位值可以在 32 位机器上按值传递。
您的代码会导致未定义的行为。在 C++ 单元中,函数声明为:
extern "C" void procWithComplex(float a, std::complex<float> *pb, std::complex<float> c, float d);
但是函数体是:
void procWithComplex(float a, complex float * pb, complex float c, float d)
不匹配。
为了帮助编译器诊断此错误,您应该避免使用预处理器为同一函数切换不同的原型。
为了避免此错误,您需要让函数原型仅使用在 C 和 C++ 中都有效的类型。就像您在
myComplex_t
示例中所做的那样。
我们最终在开发板上使用交叉编译器与本机编译器来创建二进制文件。显然,我们使用的本机编译器无法正确处理从 C 到 C++ 边界的复数。
所有建议的改变都被尝试过但都失败了,但它们仍然是很好的建议。它帮助证实了我们的想法,这可能是编译器问题,这使我们能够尝试使用交叉编译器。谢谢大家!
我希望我参加聚会还不算太晚!我制作了一个 MWE 项目,其中包含
C
/C++
互操作类型“qcomp
”,它解析为用户的本机编译器类型,即
std::complex<double>
由 C++
的 <complex>
double _Complex
由 C
的 <complex.h>
“后端”始终编译为
C++
二进制文件,而前端可以通过 C++
或 C
二进制文件进行接口,具体取决于用户的语言。
我们避免在 qcomp
和 C
二进制文件之间按值传递 C++
,这是由于缺乏一致的 ABI 而禁止的。
与M.M的解决方案相反,这不是通过创建新的
C
和C++
兼容类型。相反,我们利用 C
和 C++
复杂类型(来自 C99
和 C++11
)来保证具有 相同的布局。因此,我们可以在二进制文件之间将 pointers 传递到 qcomp
。因此,如果我们想要的 API 按值传递 qcomp
,那么我们必须创建通过指针传递的 C
兼容的包装器。有趣的是,这一切都可以完成,以便为 C
和 C++
用户提供相同的接口,这些用户访问 API,其中 qcomp
解析为其本机复杂类型。
查看项目这里,或者下面的架构: