Maybe your function formats and signs a message that could have quite a variety of fields.
int sign(struct key *key, const char *this, const char *that)
{
    x_sign(key, this);
    ...
}
...
int result = sign(key, "This", "That");
and then you find yourself wanting to add const char* other.
Yeah... and there will be a lot of caller code to fixup (unless you want to use the other macro trick for default arguments, which I'll write up later). 
Or will there?
struct sign_args {
    const char* this;
    const char* that;
    const char* other;
};
int sign(struct key *key, struct sign_args *args)
#define sign(key, ...) sign(key, &(struct sign_args){ __VA_ARGS__ })
{
    x_sign(key,args->this);
    ...
}
and it is called the same way:
sign(key, "This", "That", "Other");
but you also get keyword arguments for free!
sign(key, .other = "Other"); // leaves this & that NULL
And this expands out to a valid compound literal expression. The temporary struct (an lvalue) is created (probably on the stack) and disposed of automatically at the end of the expression.
sign(key, &(struct sign_args){ .other = "Other" });
int batch_sign(struct key *key, struct sign_args *args);
int sign(struct key *key, struct sign_args *args) 
#define sign(key, ...) sign(key, &(struct sign_args){ __VA_ARGS__})
{
  x_with_unlocked_key(key, batch_sign, args);
  ...
}
Is it efficient? Every time the args are passed, only a pointer is passed, so that can be efficient; but the struct does have to be created once and may be a little more work than passing a struct as an argument or passing the arguments.
The main points to note are that
The main points to note are that
- the original arguments should keep their same order at the start of the args struct
 - as the macro has the same name as your function, be sure to define it AFTER your function
 - the arguments are passed by pointer and so shared as they are tunnelled. 
This can make it easy to pass back a temporary value from an auto-variable. 
int sign(struct key *key, struct sign_args *args)
#define sign(key, ...) sign(key, (struct sign_args){ __VA_ARGS__ })
{
    x_sign(key,args.this);
    ...
}
it is called the same way:
sign(key, "This", "That", "Other");
but expands out slightly differently, passing the struct instead of a pointer to it, e.g.:
sign(key, .other = "Other"); // leaves this & that NULL
which this expands out to:
sign(key, (struct sign_args){ .other = "Other" });