extern "C" 静态数组函数参数

extern "C" static array function parameter

我想与我从 C++ 程序编写的 C 库进行交互。 C 库使用现代 C 编写,并使用 static 数组说明符来显示数组的最小长度,或者指针不能是 NULL.

当我尝试使用此功能编写与 extern "C" 函数接口的程序时,我收到以下消息:

error: static array size is a C99 feature, not permitted in C++

是否无法与此 C 库接口?我是否必须修改 C 库,或者是否有可用的替代方案?

这是导致错误的示例程序:

// foo.h

#ifndef FOO_H
#define FOO_H

void foo(int i[static 1]);

#endif //FOO_H
// foo.c
#include <stdio.h>

void foo(int i[static 1]) {
    printf("%i", i[0]);
}
// main.cpp

extern "C"
{
    void foo(int i[static 1]);
}

int main() {
    int i[] = {1};
    foo(i);
}

extern "C" 向 C++ 编译器指示函数名称不应被破坏。由于您正在链接外部库,因此期望外部库具有一个名为 foo 的函数(并且只有一个函数)。 C99 及以后的数组大小中的 static 关键字告诉编译器 "this array will be at least this size",这可能允许编译器进行某些优化(我不知道这些可能是什么优化,但考虑到它可能会循环展开到 N = 4,你在其中声明 void foo(int i[static 5]); 如果你传递的数组不是至少这个大小,你可能会遇到麻烦。

直接的解决办法就是我们需要告诉C++编译器:

  1. 有一个函数叫做foo
  2. 它接受一个int *作为参数
extern "C"
{
    void foo(int i[]);
}

但是在 C++ 程序中使用此函数的任何人都无法得知此函数的大小必须至少为 N(这就是数组大小中的 static 关键字的含义)。我想不出一个好的方法来强制对此进行编译时检查,除非可能通过某种类型的模板化包装函数:

#include <cstddef>

extern "C"
{
    void foo(int i[]);
}

template <std::size_t N>
void c_foo(int i[N])
{
    static_assert(N >= 5);
    foo(i);
}

int main(int argc, char** argv)
{
    int a[5] = {1, 2, 3, 4, 5};
    int b[4] = {1, 2, 3, 4};

    c_foo<5>(a); // this will be fine
    c_foo<4>(b); // this will raise a compile-time error
}


为了更加安全,我将您的 c_foo 函数的函数原型和任何 "safe" extern "C" 原型放在一个 c_library_interface.h 文件中,并且函数c_foo 函数的定义和另一个 c_library_interface_unsafe.cpp 文件中的任何 "unsafe" extern "C" 原型。这样,只要您不在主 C++ 文件中包含不安全文件,您就应该只能通过模板与 static 数组大小函数交互,这将进行一些大小检查。

(这是约翰回答的附加信息)

C header 在 C++ 中不正确,因此您必须修改它。

可能 [static 1] 的意图是表明不应使用空指针调用该函数。在这两种语言中都没有标准的方法来表明这一点,而且作者的选择与 C++ 不兼容。

一些主要编译器支持两种语言的 __attribute__((nonnull)),作为每个参数的后缀,或作为函数的前缀,然后应用于所有指针参数。

在我的个性化 header 中,我定义了一个预处理器宏,它扩展为每个编译器的等效语法,或者为不支持它的编译器留空。

请记住,没有要求编译器强制执行该行为,并且肯定会有不强制执行的情况(例如,传递一个它不知道的接收到的指针)。

所以恕我直言,根据当前编译器对此功能的态度(无论是属性还是 static 1),这应该被视为用户文档的一种形式。

我实际上已经决定不在我自己的代码中使用它,经过一些实验:使用这个属性将导致编译器优化函数 body 中的任何 null-pointer 检查,这引入了运行时错误的可能性,因为没有有效防止空指针被传递。为了使该功能可用,编译器还必须在调用函数时发出诊断,并且编译器不能保证参数是 non-null。 (这是我希望在编译器中看到的一个选项,但据我所知,目前还不存在)。