-1

I know the title is confusing but i don't know how to describe it better, let code explains itself:

I have a third-party library defines complex scalar as

typedef struct {
    float real;
    float imag;
} cpx;

so complex array/vector is like

cpx array[10];
for (int i = 0; i < 10; i++)
{
    /* array[i].real and array[i].imag is real/imag part of i-th member */
}

current situation is, in a function I have two float array as arguments, I use two temporarily local complex array like:

void my_func(float *x, float *y) /* x is input, y is output, length is fixed, say 10 */
{
    cpx tmp_cpx_A[10]; /* two local cpx array */
    cpx tmp_cpx_B[10];
    
    for (int i = 0; i < 10; i++) /* tmp_cpx_A is based on input x */
    {
        tmp_cpx_A[i].real = do_some_calculation(x[i]);
        tmp_cpx_A[i].imag = do_some_other_calculation(x[i]);
    }

    some_library_function(tmp_cpx_A, tmp_cpx_B); /* tmp_cpx_B is based on tmp_cpx_A, out-of-place */
    
    for (int i = 0; i < 10; i++) /* output y is based on tmp_cpx_B */
    {
        y[i] = do_final_calculation(tmp_cpx_B[i].real, tmp_cpx_B[i].imag);
    }
}

I notice that after first loop x is useless, and second loop is in-place. If I can build tmp_cpx_B with same memory as x and y, I can save half of intermediate memory usage.

If the complex array is defined as

typedef struct{
    float *real;
    float *imag;
} cpx_alt;

then I can simply

cpx_alt tmp_cpx_B; 
tmp_cpx_B.real = x; 
tmp_cpx_B.imag = y;

and do the rest, but it is not.

I cannot change the definition of third library complex structure, and cannot take cpx as input because I want to hide internal library to outside user and not to break API.

So I wonder if it it possible to initialize struct array with scalar member like cpx with scalar array like x and y

Edit 1: for some common ask question:

  1. in practice the array length is up to 960, which means one tmp_cpx array will take 7680 bytes. And my platform have total 56k RAM, save one tmp_cpx will save ~14% memory usage.
  2. the 3rd party library is kissFFt and do FFT on complex array, it define its own kiss_fft_cpx instead of standard <complex.h> because it can use marco to switch bewteen floating/fixed point calculation
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 2
    You can't use the same memory. `x` points to an array of `float`, not an array of `cpx`. – Barmar Oct 18 '21 at 05:50
  • "I notice that after first loop x is useless" Do you mean `x` is not used anymore? – Support Ukraine Oct 18 '21 at 05:52
  • Hard to say for sure without seeing [A Minimal, Complete, and Verifiable Example (MCVE)](http://stackoverflow.com/help/mcve) showing how the arrays passed as `x` and `y` are declared and initialized. – David C. Rankin Oct 18 '21 at 05:59
  • As commented by Barmar the answer is no. An array of floats can't be used for holding another type. Sinse you can change the parameters of `my_func` (I assume), it's possible to do some tricks (e.g. using a union type) but is it worth it? Here you only have 10 array elements so it's actually very little memory we are talking about. If it's 10.000.000 elements then it could be worth to do special tricks but not things as small as this. It's better to keep it simple. – Support Ukraine Oct 18 '21 at 06:06
  • @4386427 in practice the array length is up to 960, which means one `tmp_cpx` array will take 7680 bytes. And my platform have total 56k RAM, this will save ~14% memory usage. – ZinGer_KyoN Oct 18 '21 at 06:18
  • okay. In that case it may be worth doing special stuff. How about packing `x` and `y` into a single `cpx` array. – Support Ukraine Oct 18 '21 at 06:20
  • 1
    Please don't invent your own definitions of existing terms. In the C language, the arithmetic and pointer types are _scalar_. As opposed to structs and arrays that are _aggregate types_ ("container types" if you will). Therefore `cpx` is an aggregate type, not a scalar type. – Lundin Oct 18 '21 at 06:25
  • @4386427 this is one way that I considered before, but like the original question says, I don't want to expose internal library(i.e. `cpx`) to outside user, and don't want to break existing API which take `float *x` and `float *y` as parameters. – ZinGer_KyoN Oct 18 '21 at 06:26
  • Also, is there a reason you aren't using the built-in complex number system of C? – Lundin Oct 18 '21 at 06:29
  • Unrelated: How much memory do you have available for your stack? Is it safe to have `tmp_cpx_A/B` on stack (i.e. as objects with automatic storage duration) – Support Ukraine Oct 18 '21 at 06:31
  • @Lundin here scalar means 'signle numerical number' (on the contrary of vector and matrix) in mathmatical domain. seems it get confused in CS domain. As for built-in complex... the third-party library define and use `cpx`, not me – ZinGer_KyoN Oct 18 '21 at 06:32
  • @ZinGer_KyoN C uses pretty much the same definitions except structs are not regarded as scalars. – Lundin Oct 18 '21 at 06:33
  • @4386427 I can't tell... some other thing may run at same time and I don't have control. So I can just optimize my algorithm and try to use less resources – ZinGer_KyoN Oct 18 '21 at 06:45
  • @ZinGer_KyoN Re stack memory you say "I can't tell..." You need to find out! When programming small embedded systems with limited memory resources, you need to know and understand your systems resource usage. You can't just hope for the best! Apparently your code defines two objects on the stack taking up ~30% of the whole systems memory resources. That's a big read flag. Don't do it unless you fully understand how your system is working, i.e. does your thread/process have that kind of stack memory available. – Support Ukraine Oct 18 '21 at 07:42

2 Answers2

1

If you want standard compliant code, you can't reuse the memory pointed to by x and y to hold an array of cpx with the same dimension as the x/y arrays. There are several problems with that approach. The size of the x array plus size of the y array may not equal size of cpx array. The x and y arrays may not be in consecutive memory. Pointer type punning is not guaranteed to work by the C standard.

So the short answer is: No, you can't

However, if you are willing to accept code that isn't 100% standard compliant, it's very likely that in can be done. You'll have to check it very carefully on your specific system and accept that you can't move the code to another system without again checking it very carefully on that system (note: by system I mean cpu, compiler and it's version and so on).

There are some things you need to ensure

  1. That the x and y arrays are consecutive in memory

  2. That the cpx array has the same size as the two other arrays.

  3. That alignment is ok

If that holds true, you can go for a non-standard type punning. Like:

#define SIZE 10

// Put x and y into a struct    
typedef struct {
    float x[SIZE];
    float y[SIZE];
} xy_t;

Add some asserts to check that the memory layout is without any padding.

assert(sizeof(xy_t) == 2 * SIZE * sizeof(float));
assert(sizeof(cpx) == 2 * sizeof(float));
assert(sizeof(cpx[SIZE]) == sizeof(xy_t));
assert(alignof(cpx[SIZE]) == alignof(xy_t));

In my_func change

cpx tmp_cpx_A[SIZE];
cpx tmp_cpx_B[SIZE];

to

cpx tmp_cpx_A[SIZE];
cpx* tmp_cpx_B = (cpx*)x;  // Ugly, non-portable type punning 

This is the "dangerous" part. Instead of defining a new array, type punning through pointer casting is used so that tmp_cpx_B points to the same memory as x (and y). This is not standard compliant but on most systems it's likely to work when the above assertions hold.

Now call the function like:

xy_t xt;
for (int i = 0; i < SIZE; i++)
{
    xt.x[i] = i;
}
my_func(xt.x, xt.y);

End note As pointed out several times, this approach is not standard compliant. So you should only do this kind of stuff if you really, really need to reduce your memory usage. And you need to check your specific system to make sure it will work an your system.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
0

First of all, please note that C has a standardized library for complex numbers, <complex.h>. You might want to use that one instead of some non-standard 3rd party lib.


The main problem with your code might be execution speed, not memory usage. Allocating 2 * 10 * 2 = 40 floats isn't a big deal on most systems. On the other hand, you touch the same memory area over and over again. This might be needlessly inefficient.

Consider something like this instead:

void my_func (size_t size, const float x[size], float y[size])
{
  for(size_t i=0; i<size; i++)
  {
    cpx cpx_A = 
    {
      .real = do_some_calculation(x[i]),
      .imag = do_some_other_calculation(x[i])
    };
    cpx cpx_B;

    // ensure that the following functions work on single variables, not arrays:
    some_library_function(&cpx_A, &cpx_B);
    y[i] = do_final_calculation(cpx_B.real, cpx_B.imag); 
  }
}

Less instructions and less branching. And as a bonus, less stack usage.


In theory you might also gain a few CPU cycles by restrict qualifying the parameters, though I didn't spot any improvement when I tried that on this code (gcc x86-64).

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • thanks for answer, but it does not suit my case. that `some_library_function` is a fft which need array as input and is out-of-place. As for standard complex and array length, see my edit. – ZinGer_KyoN Oct 18 '21 at 07:12
  • @ZinGer_KyoN Well, your pseudo-code like example isn't very helpful. I very much doubt that a sane library is taking arrays as parameters without also passing on the size. – Lundin Oct 18 '21 at 07:28