0

I can give specific details of implementation if needed but the general question is the following:

Is it possible to declare a function in a header file.h with unknown type of parameters, which will be specified only in file.c?

c++Template-like solution would not solve my case as I don't want different parameter types possible as inputs of my function, the definition only considers one case, and I'd rather not have this option. I just want the declaration in the header to ignore the parameter type, but just to inform other files of the existence of this function. Is it doable or should I reconsider my problem?

Details of implementation: the function was declared static in file.c, and I wanted to remove the static to be able to use it elsewhere by including the related file.h header.

Binou
  • 124
  • 9
  • 1
    I'm not sure how that's useful - how would the calling code know the types of parameters to pass? Yes, you could do this with varargs I suppose but I doubt that's what you want. – Rup Jan 10 '20 at 13:27
  • 2
    It is not clear to me why you need to do this. – alk Jan 10 '20 at 13:28
  • I assumed that provided #include "file.h", the other files would 'look' for the function definition, and hence know the type of the parameters. My main issue is that this type is a struct defined in another file that may cause multiple macro definitions if I include it directly into the header file. – Binou Jan 10 '20 at 13:29
  • Or is it that you need to pass a pointer to a structure, you just don't want to expose the structure definition everywhere? You could declare it with void* instead. But depending on your platform your calling convention might need to know the correct number and size of arguments to pass at least. – Rup Jan 10 '20 at 13:29
  • You want the function's return type to be "unknown" as well? – alk Jan 10 '20 at 13:32
  • No, only the function parameter's type, as the type is related to multiple includes that I do not want to move from file.c to header file.h – Binou Jan 10 '20 at 13:35
  • @Binou but in order to call it correctly the calling code still needs those types defined. It's better to just move the necessary includes into the header. – Kevin Jan 10 '20 at 13:38
  • @Kevin thank you I'll try to get rid of compilation errors with that then – Binou Jan 10 '20 at 13:40
  • Perhaps read about "*opaque*" data types and how to implement them and when and where to use them? – alk Jan 10 '20 at 13:42
  • 1
    Pass a pointer to the struct instead. That way, the type doesn't need to be complete. But really, you shouldn't be providing functions that aren't usable - perhaps you're declaring them in the wrong place? – Toby Speight Jan 10 '20 at 13:48
  • *"the type is related to multiple includes that I do not want to move from file.c to header file.h"* You may want to explicitly state that in your question, to better clarify the issue you are facing (which *may* be solved by forward declaring the types of the arguments). – Bob__ Jan 10 '20 at 13:56

5 Answers5

3

Your comment:

Maybe details of implementation would give more insight. I am working on https://github.com/bitcoin-core/secp256k1, trying to turn a static function static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) non-static and declare it into the header file with same name, without moving all the needed includes related to parameters type.

This problematic because in the referenced codebase the typedef names alias anonymous structures (typedef struct { ... } secp256k1_context;, etc.).

If they aliased tagged structs (typedef struct tag typedef_name; or typedef struct tag {...} typedef_name;), you could simply do:

//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);

but it doesn't, so you do need to include the definitions of the parameter types. (As Vlad from Moscow mentions, you could use a prototypeless declaration in this particular case, but then you lose type safety.)

This is a prime example of why having typedefs to anonymous aggregates as part of an API is a bad idea.

It's best to keep the struct as part of the API or use typedefs to structs that are predictably tagged, e.g., typedef struct file_tp file_tp; (if stdio.h did this, we wouldn't have to #include <stdio.h> every time we wanted to just pass a a file-pointer along, but it instead presents a non-forward-declarable FILE, which necessitates stdio.h inclusions even where they could be avoided.)

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • Even assuming the structures had tags, the interface requires some mechanism (other than the function under discussion) to create an interesting (non-NULL) `secp256k1_context *` — and you can't declare a local variable of the structure type, so there must be at least one other function around that can return a suitably initialized `secp256k1_context *` so that it can be passed to this function. Similar comments apply to the other two arguments. The prototype-less function specification that Vlad mentions is zero help in this context; you've got to be able to provide appropriate pointers. – Jonathan Leffler Jan 11 '20 at 02:37
  • Appropriate functions may be available — but they'll be declared in a header with the appropriate type information, etc, so the whole business of declaring this function without the headers becomes moot. Its header should include the other header that declares the appropriate information, and also declare the currently static function as non-static. IMO, anything else is unlikely to work at all well. – Jonathan Leffler Jan 11 '20 at 02:40
  • @JonathanLeffler The details of this particular code aside, this general codebase-decoupling technique of using forward-declarable types does have useful use cases. Sometimes you don't need the struct-body in your API header at all: sometimes you can have a `struct foo *foo_new(...);` function instead. Other times, the struct bodies can be in some other public header, while a header that just forward-declares `struct foo;` without fully defining it isn't mainly about `struct foo` but about mostly non-`foo` related prototypes some of which may pass along `struct foo` pointers. – Petr Skocik Jan 11 '20 at 07:41
1

Yes, you may do this. Just declare the function without the parameter list.

For example

int my_function();

From the C Standard (6.7.6.3 Function declarators (including prototypes))

3 An identifier list in a function declarator that is not part of a definition of that function shall be empty.

and

  1. … The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

Take into account that in this case you will have some restrictions like the default argument promotions.

Also maybe you should also consider a generic selection as an alternative.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 1
    no. it is the best way to have UBs without warnings. https://godbolt.org/z/CmL7Fd – 0___________ Jan 10 '20 at 13:36
  • @P__J__ If you are a bad programmer then of course you can invoke undefined behavior. even in a simple code.:) – Vlad from Moscow Jan 10 '20 at 14:21
  • @P__J__: Yes. The fact that something used incorrectly can result in behavior not defined by the C standard does not negate, and is not relevant to, the fact that something used correctly can be a solution to a problem. – Eric Postpischil Jan 10 '20 at 14:22
  • Note that empty parentheses in function declarators are obsolescent and their use is discouraged in the Standard; this feature may be removed in a future Standard (although it hasn't yet been removed since C89). – ad absurdum Jan 11 '20 at 06:09
0

The only reason of declaring the function prototype is to let other code what parameters it takes and what return type function is. So your idea does (IMO) not make any sense

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    Maybe details of implementation would give more insight. I am working on https://github.com/bitcoin-core/secp256k1, trying to turn a static function `static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey)` non static and declare it into the header file with same name, without moving all the needed includes related to parameters type – Binou Jan 10 '20 at 13:34
  • Do it properly. – 0___________ Jan 10 '20 at 13:37
  • 1
    Thanks for being so nice. That means? – Binou Jan 10 '20 at 13:39
  • @Binou it means - stick to the Occam's razor rule – 0___________ Jan 10 '20 at 13:51
0
static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge,
                                 const secp256k1_pubkey* pubkey)

Since you're only dealing with pointers to structs, you just need typedefs and incomplete struct definitions that you can take from the .h files where they are defined to give the compiler enough context for the declaration. This is easy for the ones that are typedefs of structs, e.g. put this line above your function declaration:

typedef struct secp256k1_context_struct secp256k1_context;

It's safe to make an identical redeclaration, so this will work fine with or without the full headers.

The other two types are more problematic because they are typedefs of anonymous structs. The best way to do this I think is to give it a fake declaration if we don't have the real one, e.g.

#ifndef SECP256K1_H
typedef void secp256k1_pubkey;
#endif

#ifndef SECP256K1_GROUP_H
typedef void secp256k1_ge;
#endif

(You could use the real secp256k1_pubkey definition, but the _ge one depends on fields which can vary and I guess having enough context to choose the correct fields is what you were trying to avoid in the first place.)

This is slightly fragile though against changes to the library, and requires you include the secp256k1 headers before this file if at all. (Else you'll get compiler errors: make a note here in a comment about what you've done so if anyone finds this in the future they'll know how to fix it.)

You then have enough context for the function definition in your own header. Note that any calling code that isn't just passing these structures through from somewhere else will probably want the full struct definitions anyway though: why not just put this function declaration in a new .h that requires the full headers included anyway, that only a few specific places need?

Rup
  • 33,765
  • 9
  • 83
  • 112
  • Declaring an empty structure is not defined by the C standard. Even if it were, or the code were modified to declare something inside the structure, this will not work because the calling code will be passing a structure with different contents than the called code expects. – Eric Postpischil Jan 10 '20 at 14:22
  • Oh OK - I tested it in GCC and it worked. `typedef void secp256k1_pubkey;` would work instead then I guess. As I said, code that actually calls this that isn't just passing on pointers it got from somewhere else would need the full definitions. But if you just want to declare the function then placeholders are enough since it's only passing pointers. – Rup Jan 10 '20 at 14:24
  • I dont know if that is related to @EricPostpischil comment but giving the fake declaration of `secp256k1_ge` to the header secp256k1.h, just before the declaration of the function moves the problem from this header file to my extern file calling the function. I haven't tried compiling but VS Code gives me this message `incomplete type is not allowed` which I don't really get.. – Binou Jan 10 '20 at 17:10
  • These are just enough information to process the function declaration, but obviously not the real structure definitions. Your extern file that calls the function needs the full structure definitions from the real headers if it's doing anything with the structures themselves. – Rup Jan 10 '20 at 17:12
  • Oh right I completely missed that part. To go back to your potential solution, I actually need to do this for two functions, but the other one has parameter `const secp256k1_gej& gej`. I believe it wouldn't work with the typedef approach as (if i'm right) a reference to void is not allowed, is it? – Binou Jan 10 '20 at 17:25
  • Oh, this is actually C++? Probably not, no. You could try my original `typedef struct {} secp256k1_gej;` instead of void then, even if Eric says it's not supported by the standard. It worked fine in GCC at least. – Rup Jan 10 '20 at 17:28
  • However before you get too stuck into this I still really think you ought to move these definitions into a separate header file where you can assume that you have the full set of structure definitions, and only include that in the places where you need this. Hacking this into some other file is only make work for yourself, and I can't really see the benefit. – Rup Jan 10 '20 at 17:28
  • Well this file is cpp, I was hoping that the c solution would work for both. I will probably make a separate header file later but I'd really like to make this work first in order to understand a bit better. – Binou Jan 10 '20 at 17:32
-2

You can use either an function with no parameters:
int my_function(); or
a function definition with void * parameters
int my_function(void *p1, void *p2 ...);

See Sangeeth Saravanaraj's comment

Is this a good idea? Certainly No.

  • `void *` and `struct something *` are not compatible. The C standard requires all pointers to structures to have the same size and representation, so you can pass a `struct foo *` for a `struct bar *`, as long as it gets converted to the proper type before use, but this is not true of `void *`. – Eric Postpischil Jan 10 '20 at 14:20
  • I am not saying that void * and struct something * are not compatible. I am saying that you can pass unknown parameters, that you decode inside the function (typecast to the correct type).
    Is this a good idea (to allow unknown parameters to be passed) ? For sure NO, but is a possibility.
    – Catalin Dumitrescu Jan 13 '20 at 05:51
  • OP’s question has been interpreted as declaring a function one way in a header file and a different way in the source file. If this is done with a declaration of either `int my_function()` or `int my_function(void *p1, void *p2 …)` in the header file and a definition of `int my_function(struct foo *p1, struct bar *p2 …)` in the source file, then the fact that the types are not compatible renders the behavior not defined by the C standard. If you mean to propose a different solution, then you need to expand the answer with fuller statements and example code for both header and source. – Eric Postpischil Jan 13 '20 at 12:25