Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complex numbers passed by-value from C++ to C does not seem to work on powerpc

When I'm passing a complex float(complex.h) from a c++ caller to a c library, the value does not pass correctly when running on a 32 bit power pc. I was using two different open source software libraries when I detected this problem. I've isolated it down to the boundry of when C++ is passing a complex value type to a pure C type function. I wrote up some simple code to demonstrate it.

#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 */

The source C file is as follows

#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);
}

The calling C++ program is as follows

#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;
}

Normally I would expect the output to be

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

But when I run the source on an embedded power pc machine I get output that shows that the value type for complex is not being passed properly.

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

I checked the sizeof the paramaters from gdb and from both the calling and the function frame the sizes are 4 bytes, 4 bytes, 8 bytes and 4 bytes, for the float, complex float pointer, complex float, and float.

I realize I can just change the complex value parameter as a pointer, or my own struct when crossing the c++ to c boundry, but I want to know why I can't pass a complex value type from c++ to c on a power pc.

I created another example only this time I dumped some of the assembly as well as the register values.

int x = 22;
std::complex<float> y = 55 + 88I;
int z = 77;
void simpleProc(int x, complex float y, int z)

Right before call where the parameters are passed in.

x = 22
y =  {_M_value = 55 + 88 * I} 
Looking at raw data *(int*)&y = 1113325568
z = 77

This should be assembly code where it saves the the return address and saves the paramters to pass into the routine.

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>  

Looking at the assembly right after the branch :)

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  

These are the value once we are completely in the routine (after variable values are assigned.

x = 22
y =  1.07899982e-43 + 0 * I
z = 265134296

$r3 = 22
$r4 = 0x9ffff938
*(int*)$r4 = 1113325568
$r5 = 77

*(int*)(&y) = 77

My laymans view is it looks like the C++ is passing the complex value type as a reference or pointer type? but C is treating it like a value type? So is this a problem with gcc on the power pc? I am using gcc4.7.1. I am in the process of building gcc4.9.3 as a cross compiler on another machine. I will update this post either way once I get the output from a newer compiler working.

Having problems getting the cross compiler working, but looking at the memory dump of the original problem, it does show that on the power pc platform, the complex value is not being passed by value. I put the example of the struct here to show that a 64 bit value can be pass through by value, on a 32 bit machine.

like image 848
dan Avatar asked Mar 24 '16 16:03

dan


People also ask

Does C support complex numbers?

The C programming language, as of C99, supports complex number math with the three built-in types double _Complex, float _Complex, and long double _Complex (see _Complex).

How do you write complex numbers in CPP?

The complex library implements the complex class to contain complex numbers in cartesian form and several functions and overloads to operate with them. real() – It returns the real part of the complex number. imag() – It returns the imaginary part of the complex number.

How do you multiply complex numbers in C?

To multiply two complex numbers a + ib and c + id , we perform (ac - bd) + i (ad+bc) . For example: multiplication of 1+2i and 2+1i will be 0+5i.


1 Answers

Your code causes undefined behaviour. In the C++ unit the function is declared as:

extern "C" void procWithComplex(float a, std::complex<float> *pb, std::complex<float> c, float d);

but the function body is:

void procWithComplex(float a, complex float * pb, complex float  c, float d)

which does not match.

To help the compiler diagnose this error you should avoid using the preprocessor to switch in different prototypes for the same function.

To avoid this error you need to have the function prototype only use types which are valid in both C and C++. Such as you did in the myComplex_t example.

like image 163
M.M Avatar answered Oct 18 '22 08:10

M.M