如何安全地将一个float *x转换为const float *const *y?

4

我有一个函数audioReceived(float * input, int bufferSize, int nChannels),我想在其中调用一个需要const float *const *inputBuffers的库函数。

显然,强制转换const float *const *inputBuffers = (const float* const*)input;虽然可以编译通过,但是这样做是个坏主意,会导致程序崩溃、杀死小猫等问题。没有人需要修改原始的float* input,它是正在处理的音频数据。

我该如何正确地实现呢?

编辑:这里有更多的代码。 audioReceived是:

void testApp::audioReceived (float * input, int bufferSize, int nChannels){ 

     Vamp::RealTime rt = Vamp::RealTime::fromMilliseconds(ofGetSystemTime());
     float const *const tmp[] = { input, 0 };    
     Vamp::Plugin::FeatureSet fs = myPlugin->process(tmp, rt);
 }

库函数process在基类中被定义:

 /**
 * Process a single block of input data.
 * 
 * If the plugin's inputDomain is TimeDomain, inputBuffers will
 * point to one array of floats per input channel, and each of
 * these arrays will contain blockSize consecutive audio samples
 * (the host will zero-pad as necessary).  The timestamp in this
 * case will be the real time in seconds of the start of the
 * supplied block of samples.
 *
 * If the plugin's inputDomain is FrequencyDomain, inputBuffers
 * will point to one array of floats per input channel, and each
 * of these arrays will contain blockSize/2+1 consecutive pairs of
 * real and imaginary component floats corresponding to bins
 * 0..(blockSize/2) of the FFT output.  That is, bin 0 (the first
 * pair of floats) contains the DC output, up to bin blockSize/2
 * which contains the Nyquist-frequency output.  There will
 * therefore be blockSize+2 floats per channel in total.  The
 * timestamp will be the real time in seconds of the centre of the
 * FFT input window (i.e. the very first block passed to process
 * might contain the FFT of half a block of zero samples and the
 * first half-block of the actual data, with a timestamp of zero).
 *
 * Return any features that have become available after this
 * process call.  (These do not necessarily have to fall within
 * the process block, except for OneSamplePerStep outputs.)
 */
virtual FeatureSet process(const float *const *inputBuffers,
               RealTime timestamp) = 0;

在实际的标题中:

FeatureSet process(const float *const *inputBuffers, Vamp::RealTime timestamp);

我认为EXC_BAD_ACCESS可能来自于库函数需要一个零填充数组,而我没有提供(a)这听起来合理吗?(b)如果是这样,是时候在SO上问一个不同的问题了吗?
感谢大家迄今为止的帮助,它非常启发性/澄清/教育/有趣。

这不太有意义。你从一个指向浮点数的指针开始,想要得到一个指向指针的指针,而且里面还有很多const修饰符。什么? - Lightness Races in Orbit
这个问题为什么要打 c++ 的标签呢?它不是纯粹的 C 问题吗? - sbi
说实话,当涉及到const时,我也会问“啥?”--但这是特征提取库所需的,我试图首先按照它的要求开始,然后再尝试重写库。 (这可能只会导致更多的混乱;我的工作假设是编写该代码的人比我知道得多--尽管可能不比你们中的一些人知道得多。) - buildsucceeded
在函数定义之前的注释中,“inputBuffers将指向每个输入通道的一个浮点数数组”。这里只有一个输入通道。因此,我从一个浮点数数组(float * input)解释为包含该数组的数组(float const *const tmp[] = { &input, 0 }`,如下所示)。从概念上讲是有意义的,但是后者会导致编译错误。 - buildsucceeded
@sbi:命名空间和成员函数是在什么时候加入到C语言中的? - Lightness Races in Orbit
显示剩余8条评论
4个回答

3
语义在这里很重要。从参数名称可以猜出,您想调用的函数接受多个缓冲区,因此需要指向浮点数的指针数组(即数组的数组)。由于您只有一个数组,因此需要创建一个包含原始指针的数组,并将其传递给该函数。
如果该函数具有单独的参数来传递它所接收的数组的长度(即缓冲区数量),则使用一元运算符&获取地址并传递长度为1就足够了;否则,您需要创建一个临时的以null结尾的数组。
float const *const tmp[] = { input, 0 };

并将其传递给函数。


是的,我怀疑也是这样。但是,如果不知道,那就只是猜测!原帖作者应该阅读库文档。 - Lightness Races in Orbit
@Tomalak,我一直是个乖孩子,看了但文档并不十分清晰/完整。鉴于它是开源的,我希望能帮上忙,但首先我得弄明白它是如何工作的! - buildsucceeded
@ickydog:不,他的意思就是他所写的。你可能还涉及到了这个链接中的内容。我认为我们已经达到了从有限的信息中可以告诉你的极限。 - Lightness Races in Orbit
实际上,我是指没有&。已更正。 - Simon Richter

2

对于转换本身,只需执行&input即可。请注意,内部函数的参数是指向指针的指针。

编辑:

为了按照原问题中的评论要求获取以空值结尾的输入缓冲区列表,您可以使用以下方法:

float const * const buffers[] = {input, 0};

1
这可能足够了。这取决于被调用的函数。 - Simon Richter
@Kevin:我意识到我的回答不适用于此处(因为该函数需要一个const char * float *,而不是const float **),所以我将其删除了。如果@Juraj对他的回答进行空编辑,我可以取消我的踩。 - Oliver Charlesworth
@Oli:我不明白你在这里的观点。转换本身是正确的。而且在OP提到所需格式的注释之前,我就已经发布了答案。问题是关于强制类型转换的,我认为我已经回答了。不过我可能会编辑答案。 - Juraj Blaho
@Juraj:我同意你的答案是正确的(尽管@Tomalak提出的问题除外)。问题在于SO实施了一个规则,即你不能在投票后超过5分钟更改投票,除非回答被编辑。如果你对回答进行任何修改,我就可以取消我的(错误的)反对票! - Oliver Charlesworth
@Oli:已编辑并扩展以回答额外的问题。 - Juraj Blaho
显示剩余2条评论

1
你需要传递一个float **,也就是指向浮点数的指针的指针。 const只是告诉你这个函数不会修改任何被指向的值。
如果你有一个可能可变数量的通道nChannels,每个通道有bufferSize个浮点数,那么你可以使用以下方式设置必要的存储空间。
float **data = new float *[nChannels];
for (int c = 0; c < nChannels; ++c) {
    data[c] = new float[bufferSize];
}

之后数据将以正确的格式传递给此函数。(该函数知道这些内容有多大,因为您之前已经向插件构造器提供了通道计数和块大小。)

您的示例代码显示您具有单指针float *,这表明您可能从交错格式的数据开始(即在单个数组中交替通道值)。如果是这种情况,您需要将其解交错成单独的通道,然后传递给process函数:

for (int i = 0; i < bufferSize; ++i) {
    for (int c = 0; c < nChannels; ++c) {
        data[c][i] = input[i * nChannels + c];
    }
}
myPlugin->process(data, rt);

最后,不要忘记delete[]成员指针以及双指针。
for (int c = 0; c < nChannels; ++c) {
    delete[] data[c];
}
delete[] data;

如果您知道您只会有一个数据通道,并且在插件构造函数中已经提供了1作为通道计数,那么您可以像其他人建议的那样进行额外的间接操作:

myPlugin->process(&input, rt);

(提示:我编写了引起提问者困惑的代码)
注:保留原文中的html标签。

1
float *

不是相同的

float **

因此将input转换为inputBuffers是行不通的。

通常,从非const到const的转换是隐式的,您不需要做任何特殊处理。但是从const到非const的转换并不那么容易。这在您思考时是合乎逻辑的。


这是正确的,但有一些重要的注意事项。例如,您不能从T **隐式转换为const T ** - Oliver Charlesworth
1
没问题。T** 不能隐式转换为 T const**,但可以转换为 T const* const* - neuront

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接