extern "C" stati c параметр функции массива - PullRequest
6 голосов
/ 04 марта 2020

Я бы хотел взаимодействовать с библиотекой C, которую я написал из программы на C ++. Библиотека C написана с использованием современного C и использует спецификатор массива static, чтобы показать минимальную длину массива, или указатель не может быть NULL.

Когда Я пытаюсь написать программу, которая взаимодействует с функцией extern "C", используя эту функцию, я получаю следующее сообщение:

ошибка: stati 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);
}

Ответы [ 2 ]

6 голосов
/ 04 марта 2020

extern "C" указывает компилятору C ++, что имя функции не должно быть искажено. Поскольку вы ссылаетесь на внешнюю библиотеку, ожидается, что во внешней библиотеке есть функция (и только одна функция) с именем foo. Ключевое слово static в C99 и далее в размере массива говорит компилятору, что «этот массив будет по крайней мере такого размера», что может позволить компилятору выполнить определенные оптимизации (я не знаю, какие это могут быть оптимизации, но Учтите, что он может выполнить l oop развертывание до 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 файле. Таким образом, до тех пор, пока вы не включите небезопасный файл в свои основные файлы C ++, вы сможете взаимодействовать только с функциями размера массива static через шаблоны, которые будут выполнять некоторую проверку размера.

2 голосов
/ 04 марта 2020

(Это дополнительная информация к ответу Джона)

Заголовок C неверен в C ++, поэтому вам придется его изменить.

Возможно, цель [static 1] означает, что функция не должна вызываться с нулевым указателем. Не существует стандартного способа указать это на обоих языках, и выбор автора не совместим с C ++.

Некоторые основные компиляторы поддерживают __attribute__((nonnull)) на обоих языках, либо как постфикс к каждому параметру, либо как префикс к функции, которая затем применяется ко всем параметрам указателя.

В своем персонализированном заголовке я определяю макрос препроцессора, который расширяется до эквивалентного синтаксиса для каждого компилятора, или не включается для компиляторов, которые его не поддерживают.

Имейте в виду, что требования не требуются компилятор для обеспечения поведения, и, безусловно, будут случаи, когда он этого не делает (например, передает полученный указатель, о котором он ничего не знает).

ИМХО, учитывая текущее состояние отношения компилятора к этой функции (будь то атрибут или static 1), это следует рассматривать как форму пользовательской документации.

У меня действительно есть решил не использовать его в своем собственном коде, после некоторых экспериментов: использование этого атрибута заставит компилятор оптимизировать любые проверки нулевого указателя в теле функции, что создает возможность ошибок времени выполнения, поскольку нет эффективного предотвращения нулевых указателей проходить Чтобы сделать эту функцию пригодной для использования, компилятору также придется выполнять диагностику каждый раз, когда вызывается функция, и компилятор не может гарантировать, что аргумент не равен нулю. (Это вариант, который я хотел бы видеть в компиляторах, но, насколько я знаю, пока не существует).

...