Можно ли объявить в заголовочном файле функцию с неизвестным типом, который указан только в файле '.c'?

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

Можно ли объявить в заголовочном файле.h функцию с неизвестным типом параметров, которая будет указана только в файле.c?

C++Template-подобное решение не решит мой случай, так как я не хочу, чтобы разные типы параметров были возможными в качестве входных данных моей функции, определение рассматривает только один случай, и я бы предпочел не иметь этого вариант. Я просто хочу, чтобы объявление в заголовке игнорировало тип параметра, а просто информировало другие файлы о существовании этой функции. Это выполнимо или я должен пересмотреть свою проблему?

Детали реализации: функция была объявлена ​​static в файле.c, и я хотел удалить static, чтобы иметь возможность использовать ее в другом месте, включив соответствующий заголовок file.h. .


person Binou    schedule 10.01.2020    source источник
comment
Я не уверен, насколько это полезно - как вызывающий код узнает типы параметров, которые нужно передать? Да, я полагаю, вы могли бы сделать это с помощью varargs, но я сомневаюсь, что вы этого хотите.   -  person Rup    schedule 10.01.2020
comment
Мне непонятно, зачем вам это нужно.   -  person alk    schedule 10.01.2020
comment
Я предположил, что при условии #include file.h другие файлы будут «искать» определение функции и, следовательно, будут знать тип параметров. Моя основная проблема заключается в том, что этот тип представляет собой структуру, определенную в другом файле, которая может вызвать несколько определений макросов, если я включу ее непосредственно в файл заголовка.   -  person Binou    schedule 10.01.2020
comment
Или вам нужно передать указатель на структуру, вы просто не хотите везде выставлять определение структуры? Вместо этого вы можете объявить его с помощью void*. Но в зависимости от вашей платформы вашему соглашению о вызовах может потребоваться знать правильное количество и размер аргументов для передачи.   -  person Rup    schedule 10.01.2020
comment
Вы хотите, чтобы тип возвращаемого значения функции также был неизвестен?   -  person alk    schedule 10.01.2020
comment
Нет, только тип параметра функции, так как тип связан с несколькими включениями, которые я не хочу перемещать из файла .c в файл заголовка.h   -  person Binou    schedule 10.01.2020
comment
@Binou, но для правильного вызова вызывающему коду все еще нужны определенные типы. Лучше просто переместить необходимые включения в шапку.   -  person Kevin    schedule 10.01.2020
comment
@ Кевин, спасибо, тогда я постараюсь избавиться от ошибок компиляции.   -  person Binou    schedule 10.01.2020
comment
Возможно, вы читали о типах данных opaque и о том, как их реализовать, когда и где их использовать?   -  person alk    schedule 10.01.2020
comment
Вместо этого передайте указатель на структуру. Таким образом, тип не обязательно должен быть полным. Но на самом деле вам не следует предоставлять функции, которыми нельзя пользоваться — возможно, вы объявляете их не в том месте?   -  person Toby Speight    schedule 10.01.2020
comment
тип связан с несколькими включениями, которые я не хочу перемещать из файла.c в файл заголовка.h Вы можете явно указать это в своем вопросе, чтобы лучше прояснить проблему, с которой вы столкнулись ( который может быть решен путем прямого объявления типов аргументов).   -  person Bob__    schedule 10.01.2020


Ответы (5)


Твой комментарий:

Возможно, детали реализации дадут больше понимания. Я работаю над https://github.com/bitcoin-core/secp256k1, пытаясь превратить статическую функцию static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) в нестатическую и объявить ее в заголовочном файле с тем же именем, не перемещая все необходимые включения, связанные с типом параметров.

Это проблематично, потому что в кодовой базе, на которую ссылаются, имена typedef являются псевдонимами анонимных структур (typedef struct { ... } secp256k1_context; и т. д.).

Если бы они использовали псевдонимы помеченных структур (typedef struct tag typedef_name; или typedef struct tag {...} typedef_name;), вы могли бы просто сделать:

//forward-declare the structs (assuming tag = typedef_name)
struct secp256k1_context;
struct secp256k1_ge;
struct secp256k1_pubkey;
//use struct tag instead of typedef_name
int secp256k1_pubkey_load(const struct secp256k1_context* ctx, 
                          struct secp256k1_ge* ge, 
                          const struct secp256k1_pubkey* pubkey);

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

Это яркий пример того, почему иметь typedefs для анонимных агрегатов как часть API — плохая идея.

Лучше всего оставить struct как часть API или использовать typedef для структур с предсказуемой маркировкой, например, typedef struct file_tp file_tp; (если бы stdio.h делал это, нам не пришлось бы #include <stdio.h> каждый раз, когда мы хотим просто передать указатель на файл, но вместо этого он представляет собой FILE, не подлежащий объявлению вперед, что требует включения stdio.h даже там, где их можно было бы избежать.)

person PSkocik    schedule 10.01.2020
comment
Даже если предположить, что у структур есть теги, интерфейсу требуется какой-то механизм (кроме обсуждаемой функции) для создания интересного (не NULL) secp256k1_context * — и вы не можете объявить локальную переменную типа структуры, поэтому должно быть at по крайней мере, одна другая функция, которая может вернуть соответствующим образом инициализированный secp256k1_context *, чтобы его можно было передать этой функции. Аналогичные комментарии применимы и к двум другим аргументам. Спецификация функций без прототипов, о которой упоминает Влад, в этом контексте не помогает; вы должны быть в состоянии предоставить соответствующие указатели. - person Jonathan Leffler; 11.01.2020
comment
Соответствующие функции могут быть доступны, но они будут объявлены в заголовке с соответствующей информацией о типе и т. д., поэтому весь процесс объявления этой функции без заголовков становится спорным. Его заголовок должен включать в себя другой заголовок, который объявляет соответствующую информацию, а также объявлять текущую статическую функцию как нестатическую. ИМО, что-то еще вряд ли будет работать вообще хорошо. - person Jonathan Leffler; 11.01.2020
comment
@JonathanLeffler Помимо деталей этого конкретного кода, этот общий метод разделения кодовой базы с использованием типов с предварительным объявлением действительно имеет полезные варианты использования. Иногда вам вообще не нужен struct-body в заголовке вашего API: иногда вы можете вместо этого использовать функцию struct foo *foo_new(...);. В других случаях тела структур могут находиться в каком-то другом общедоступном заголовке, в то время как заголовок, который просто объявляет struct foo; без полного его определения, в основном относится не к struct foo, а в основном к прототипам, не связанным с foo, некоторые из которых могут передавать struct foo указатели. - person PSkocik; 11.01.2020

Да, вы можете сделать это. Просто объявите функцию без списка параметров.

Например

int my_function();

Из стандарта C (деклараторы функций 6.7.6.3 (включая прототипы))

3 Список идентификаторов в описателе функции, который не является частью определения этой функции, должен быть пустым.

а также

  1. … Пустой список в объявлении функции, который не является частью определения этой функции, указывает, что не предоставляется никакой информации о количестве или типах параметров.

Учтите, что в этом случае у вас будут некоторые ограничения, такие как продвижение аргументов по умолчанию.

Также, возможно, вам следует также рассмотреть общий выбор в качестве альтернативы.

person Vlad from Moscow    schedule 10.01.2020
comment
нет. это лучший способ иметь UB без предупреждений. godbolt.org/z/CmL7Fd - person 0___________; 10.01.2020
comment
@P__J__ Если вы плохой программист, то, конечно, вы можете вызвать неопределенное поведение. даже в простом коде. :) - person Vlad from Moscow; 10.01.2020
comment
@P__J__: Да. Тот факт, что что-то неправильно используется, может привести к поведению, не определенному стандартом C, не отменяет и не имеет отношения к тому факту, что что-то используется правильно, может быть решением проблемы. - person Eric Postpischil; 10.01.2020
comment
Обратите внимание, что пустые скобки в деклараторах функций устарели, и их использование не рекомендуется в Стандарте; эта функция может быть удалена в будущем стандарте (хотя она еще не удалена со времен C89). - person ad absurdum; 11.01.2020

Единственная причина объявления прототипа функции — позволить другому коду, какие параметры он принимает и какой у функции тип возвращаемого значения. Так что ваша идея (ИМО) не имеет никакого смысла

person 0___________    schedule 10.01.2020
comment
Возможно, детали реализации дадут больше понимания. Я работаю над github.com/bitcoin-core/secp256k1, пытаясь превратить статическую функцию static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) не статический и объявить его в заголовочном файле с тем же именем, не перемещая все необходимые включения, связанные с типом параметров - person Binou; 10.01.2020
comment
Делайте это правильно. - person 0___________; 10.01.2020
comment
Спасибо за то, что ты такой милый. Это означает? - person Binou; 10.01.2020
comment
@Binou это значит - придерживайся правила бритвы Оккама - person 0___________; 10.01.2020

static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge,
                                 const secp256k1_pubkey* pubkey)

Поскольку вы имеете дело только с указателями на структуры, вам просто нужны определения типов и неполные определения структур, которые вы можете взять из файлов .h, где они определены, чтобы дать компилятору достаточно контекста для объявления. Это легко для тех, которые являются typedefs структур, например. поместите эту строку над объявлением вашей функции:

typedef struct secp256k1_context_struct secp256k1_context;

Безопасно сделать идентичное повторное объявление, так что это будет нормально работать как с полными заголовками, так и без них.

Два других типа более проблематичны, потому что они являются определениями типов анонимных структур. Я думаю, что лучший способ сделать это - дать ему поддельную декларацию, если у нас нет настоящей, например.

#ifndef SECP256K1_H
typedef void secp256k1_pubkey;
#endif

#ifndef SECP256K1_GROUP_H
typedef void secp256k1_ge;
#endif

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

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

Тогда у вас будет достаточно контекста для определения функции в вашем собственном заголовке. Обратите внимание, что любой вызывающий код, который не просто передает эти структуры откуда-то еще, вероятно, в любом случае потребует полных определений структур: почему бы просто не поместить объявление этой функции в новый .h, который в любом случае требует включения полных заголовков, что только мало конкретных мест нужно?

person Rup    schedule 10.01.2020
comment
Объявление пустой структуры не определено стандартом C. Даже если бы это было так или код был изменен для объявления чего-то внутри структуры, это не сработает, потому что вызывающий код будет передавать структуру с другим содержимым, чем ожидает вызываемый код. - person Eric Postpischil; 10.01.2020
comment
О, хорошо - я проверил это в GCC, и это сработало. Вместо этого, я думаю, сработает typedef void secp256k1_pubkey;. Как я уже сказал, коду, который на самом деле вызывает this, а не просто передает указатели, полученные откуда-то еще, потребуются полные определения. Но если вы просто хотите объявить функцию, то достаточно заполнителей, так как это только указатели. - person Rup; 10.01.2020
comment
Я не знаю, связано ли это с комментарием @EricPostpischil, но предоставление поддельного объявления secp256k1_ge заголовку secp256k1.h непосредственно перед объявлением функции перемещает проблему из этого файла заголовка в мой внешний файл, вызывающий функцию. Я не пробовал компилировать, но VS Code выдает мне это сообщение incomplete type is not allowed, которое я на самом деле не понимаю. - person Binou; 10.01.2020
comment
Этой информации достаточно для обработки объявления функции, но, очевидно, не реальных определений структуры. Ваш внешний файл, который вызывает функцию, нуждается в полных определениях структуры из реальных заголовков, если он что-то делает с самими структурами. - person Rup; 10.01.2020
comment
Ах да, я полностью пропустил эту часть. Чтобы вернуться к вашему потенциальному решению, мне действительно нужно сделать это для двух функций, но у другой есть параметр const secp256k1_gej& gej. Я считаю, что это не сработает с подходом typedef, поскольку (если я прав) ссылка на void не разрешена, не так ли? - person Binou; 10.01.2020
comment
О, это на самом деле C++? Наверное нет, нет. Тогда вы можете попробовать мой оригинальный typedef struct {} secp256k1_gej; вместо void, даже если Эрик скажет, что это не поддерживается стандартом. По крайней мере, в GCC это работало нормально. - person Rup; 10.01.2020
comment
Однако, прежде чем вы слишком увязнете в этом, я все же думаю, что вам следует переместить эти определения в отдельный заголовочный файл, где вы можете предположить, что у вас есть полный набор определений структуры, и включать его только в те места, где вам это нужно. Взломать это в какой-то другой файл - это только работа для себя, и я не вижу в этом никакой выгоды. - person Rup; 10.01.2020
comment
Ну, это файл cpp, я надеялся, что решение c будет работать для обоих. Позже я, вероятно, сделаю отдельный заголовочный файл, но мне бы очень хотелось сначала сделать эту работу, чтобы немного лучше понять. - person Binou; 10.01.2020

Вы можете использовать любую функцию без параметров:
int my_function(); или
определение функции с параметрами void *
int my_function(void *p1, void *p2 ...);

См. комментарий Сангита Сараванараджа.

Хорошая ли это идея? Конечно нет.

person Catalin Dumitrescu    schedule 10.01.2020
comment
void * и struct something * несовместимы. Стандарт C требует, чтобы все указатели на структуры имели одинаковый размер и представление, поэтому вы можете передать struct foo * вместо struct bar *, если перед использованием он преобразуется в правильный тип, но это не относится к void *. - person Eric Postpischil; 10.01.2020
comment
Я не говорю, что void * и struct something * несовместимы. Я говорю, что вы можете передавать неизвестные параметры, которые вы декодируете внутри функции (приведение к правильному типу). ‹br› Это хорошая идея (разрешить передачу неизвестных параметров)? Конечно НЕТ, но возможно. - person Catalin Dumitrescu; 13.01.2020
comment
Вопрос OP был интерпретирован как объявление функции одним способом в заголовочном файле и другим способом в исходном файле. Если это делается с помощью объявления int my_function() или int my_function(void *p1, void *p2 …) в заголовочном файле и определения int my_function(struct foo *p1, struct bar *p2 …) в исходном файле, то тот факт, что типы несовместимы, приводит к поведению, не определенному стандартом C. Если вы хотите предложить другое решение, вам нужно расширить ответ более полными утверждениями и примерами кода как для заголовка, так и для источника. - person Eric Postpischil; 13.01.2020