【问题标题】:How to use scalar array initialize array of struct with scalar member?如何使用标量数组初始化带有标量成员的结构数组?
【发布时间】:2021-10-18 05:45:02
【问题描述】:

我知道标题很混乱但我不知道如何更好地描述它,让代码自己解释一下:

我有一个第三方库将复杂的标量定义为

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

所以复杂的数组/向量就像

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

目前的情况是,在一个函数中我有两个浮点数组作为参数,我使用两个临时的局部复数数组,例如:

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

我注意到在第一个循环之后x 没用,第二个循环就位。如果我可以使用与xy 相同的内存来构建tmp_cpx_B,我可以节省一半的中间内存使用量。

如果复数数组定义为

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

那我就可以了

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

然后做剩下的,但事实并非如此。

我无法更改第三个库复杂结构的定义,也无法将cpx 作为输入,因为我想对外部用户隐藏内部库,而不是破坏 API。

所以我想知道是否可以使用cpx 之类的标量成员和xy 之类的标量数组来初始化结构数组

编辑1:对于一些常见问题:

  1. 实际上数组长度最多为 960,这意味着一个tmp_cpx 数组将占用 7680 个字节。我的平台总共有 56k 内存,节省一个 tmp_cpx 将节省约 14% 的内存使用。
  2. 第三方库是kissFFt,对复数数组进行FFT,它定义了自己的kiss_fft_cpx而不是标准的,因为它可以使用marco在浮点/定点计算之间切换

【问题讨论】:

  • 你不能使用相同的内存。 x 指向 float 的数组,而不是 cpx 的数组。
  • "我注意到在第一个循环之后 x 没用" 你的意思是 x 不再使用了吗?
  • 如果没有看到 A Minimal, Complete, and Verifiable Example (MCVE) 显示数组是如何作为 xy 声明和初始化传递的,很难确定。
  • 正如 Barmar 所说,答案是否定的。浮点数组不能用于保存另一种类型。你可以更改my_func 的参数(我假设),可以做一些技巧(例如使用联合类型),但值得吗?在这里,您只有 10 个数组元素,因此实际上我们正在谈论的内存很少。如果它是 10.000.000 个元素,那么做一些特殊的技巧可能是值得的,但不是像这样小的事情。最好保持简单。
  • 请不要自行对现有术语进行定义。在 C 语言中,算术和指针类型是标量。与 聚合类型 的结构和数组相反(如果您愿意,可以选择“容器类型”)。因此cpx 是聚合类型,而不是标量类型。

标签: c embedded kissfft


【解决方案1】:

如果您想要符合标准的代码,则不能重用xy 指向的内存来保存与x/y 数组具有相同维度的cpx 数组。这种方法有几个问题。 x 数组的大小加上y 数组的大小可能不等于cpx 数组的大小。 xy 数组可能不在连续内存中。 C 标准不保证指针类型双关语可以正常工作。

所以简短的回答是:不,你不能

但是,如果您愿意接受并非 100% 符合标准的代码,则很有可能可以这样做。您必须在您的特定系统上非常仔细地检查它并接受您不能将代码移动到另一个系统而不再次在该系统上非常仔细地检查它(注意:系统我的意思是 cpu、编译器和它的版本等等开)。

有些事情你需要确保

  1. xy 数组在内存中是连续的

  2. cpx 数组与其他两个数组的大小相同。

  3. 对齐没问题

如果这是真的,您可以使用非标准类型的双关语。喜欢:

#define SIZE 10

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

添加一些断言来检查内存布局是否没有任何填充。

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

my_func

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

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

这是“危险”部分。不是定义一个新数组,而是通过指针转换使用类型双关语,以便tmp_cpx_B 指向与x(和y)相同的内存。这符合标准,但在大多数系统上,当上述断言成立时,它可能会起作用。

现在像这样调用函数:

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

结束说明 正如多次指出的那样,这种方法不符合标准。所以你应该只做这种事情,如果你真的,真的需要减少你的内存使用。您需要检查您的特定系统以确保它可以在您的系统上运行。

【讨论】:

    【解决方案2】:

    首先,请注意 C 有一个用于复数的标准化库 &lt;complex.h&gt;。您可能想使用那个而不是一些非标准的 3rd 方库。


    您的代码的主要问题可能是执行速度,而不是内存使用。在大多数系统上分配 2 * 10 * 2 = 40 浮点数并不是什么大问题。另一方面,你一遍又一遍地触摸同一个内存区域。这可能是不必要的低效。

    请考虑这样的事情:

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

    更少的指令和更少的分支。作为奖励,堆栈使用量更少。


    理论上,您还可以通过restrict 限定参数来获得一些 CPU 周期,但我在此代码 (gcc x86-64) 上尝试时没有发现任何改进。

    【讨论】:

    • 感谢您的回答,但它不适合我的情况。 some_library_function 是一个 fft,它需要数组作为输入并且不合适。至于标准复数和数组长度,请参阅我的编辑。
    • @ZinGer_KyoN 好吧,您的类似示例的伪代码并不是很有帮助。我非常怀疑一个理​​智的库是否将数组作为参数而不传递大小。
    猜你喜欢
    • 2019-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多