【问题标题】:extern "C" static array function parameterextern "C" 静态数组函数参数
【发布时间】:2020-06-16 10:03:37
【问题描述】:

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

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

错误:静态数组大小是 C99 功能,在 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);
}

【问题讨论】:

  • 你不能神奇地让 C++ 编译器理解 VLA。 extern "C" 仅指定语言绑定,而不是神奇地教 C++ 编译器所指的语言。
  • 如果我错了,请纠正我,但 C 中的函数签名只是一个未损坏的名称。 extern C 的意思是,“嘿,不要弄乱这个名字。这是函数签名”。您实际上不应该在 extern "C" 块中定义函数,对吗?你只是应该链接到它?所以你会写extern "C" { void foo(int i[]); } 并确保你链接到图书馆。我没有做很多(或根本没有)与 C 库的交互。我以前看过。
  • 同样的错误?我的建议虽然省略了static 关键字。你根本不应该得到那个错误。
  • 我会写一个答案。对此有一些警告。一方面,创建库的 C 编译器可能对代码进行了一些优化,期望所有传入的数组至少是给定的大小。例如,可能循环展开到大小 N?谁知道。如果编译后的代码要求数组是那个大小,而你传递的东西不够大,那么这种类型安全就会被破坏。所以调用函数时必须非常小心。我不确定如何确保安全。
  • @John 虽然在 static 1 的情况下,它是多余的,因为标准 C 和 C++ 无论如何都不支持零大小的数组。也许这应该表明“指针不应该是空指针”

标签: c++ c c99 c11


【解决方案1】:

extern "C" 向 C++ 编译器指示函数名称不应被破坏。由于您要链接到外部库,因此期望外部库有一个名为 foo 的函数(并且只有一个函数)。 C99 及以后的数组大小中的 static 关键字告诉编译器“这个数组将至少是这个大小”,这可能允许编译器进行某些优化(我不知道这些可能是什么优化,但是考虑到它可能会循环展开到 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 函数和任何“安全”extern "C" 原型的函数原型放在一个 c_library_interface.h 文件中,并将您的 c_foo 函数和任何“不安全”extern "C" 原型在另一个 c_library_interface_unsafe.cpp 文件中。这样,只要您不将 unsafe 文件包含在您的主 C++ 文件中,您就应该只能通过模板与 static 数组大小函数进行交互,这将进行一些大小检查。

【讨论】:

  • 据我所知,编译器实际上并没有实现与普通指针有任何不同的[ static n],而且这个C99 特性被误导了。为了真正提高编译时的类型安全性,编译器需要查看函数声明、函数调用和静态大小数组,所有这些都在同一个翻译单元内。
【解决方案2】:

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

C 标头在 C++ 中不正确,因此您必须对其进行修改。

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

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

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

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

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

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

【讨论】:

  • “所以恕我直言,以编译器对该特性的当前状态(无论是属性还是静态 1),这应该被视为一种用户文档形式。”这也是我的看法,我还没有看到编译器在发现 [static n] 时生成任何不同的代码。
  • @Lundin 我已经看到 gcc 删除了对 __attribute__((nonnull)) 的空指针检查,还没有检查过 [static 1],但假设他们将来可能会这样做似乎是合理的
  • 目前似乎不是这样:godbolt.org/z/w2fQah。属性 nonnull 有效, static 无效。但是,clang 为这两个示例提供了相同的机器代码。所以看来至少 clang 确实实现了这一点。
猜你喜欢
  • 1970-01-01
  • 2012-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多