1

I'd like to pass 3 arguments to thread_routine_handler like this:

makecontext(&th->context, thread_routine_handler, 3, th, start_routine, arg);

but I don't know how to read them properly. It returns the warning:

warning: passing argument 2 of ‘makecontext’ from incompatible pointer type [-Wincompatible-pointer-type void (*)(thread_t *, void (*)(), void *) {aka void (*)(struct thread *, void (*)(), void *)}

my callback routine is defined like this:

void thread_routine_handler(thread_t *th, start_routine_t start_routine, void *arg_routine)
{ ... }

It's working but I'm afraid of some UB behavior. The 2 argument of makecontext does expect a function taking no arguments. How am I supposed to read them? even with va_start() etc I do need at least one argument (argc) or am I missing something? what's the proper way to do that?

UPDATE

start_routine_t is typedef void (*start_routine_t)();

Jack
  • 16,276
  • 55
  • 159
  • 284

1 Answers1

2

The expected type for the second parameter to makecontext() is void (*)(). As the diagnostic message indicates, the type of the argument you are passing is void (*)(thread_t *, void (*)(), void *). These are not "compatible" types in the sense in which the C language spec uses that term, on account of the latter being declared with a function prototype and the former not.

However, given the particular types of the parameters to your thread_routine_handler() function, it is possible to call that via a pointer of type void (*)(), provided that the actual arguments are of the correct types. The best available solution is to apply an appropriate typecast to the makecontext() argument. If the actual arguments are not of exactly the correct types, then you should cast those, too. Thus, in full generailty:

makecontext(&th->context, (void (*)()) thread_routine_handler,
        3, (thread_t *) th, (start_routine_t) start_routine, (void *) arg);

You can omit any of those casts that convert values to (exactly) the types they already have.

But that's largely irrelevant here, because you have an issue with the other arguments that your compiler is not diagnosing (understandably). The variable arguments to makecontext(), those that are forwarded to the specified function, are required to all be of type int (thanks, @user253751). Their number can vary, but not their types. I overlooked that in the original version of this answer.

It follows that thread_routine_handler() can accept any number of arguments compatible with type int or unsigned int, but none of any other type. You will still need to cast the pointer to the start function, as already described, but you cannot safely use the particular function you are trying to use. Possibly you can set up a global, dynamically-allocated table of the th, start_routine, and arg triples, and pass an index into that.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    this is safe, i.e. not will not cause undefined behavior or something like that, right? – Jack Feb 22 '23 at 19:03
  • The `makecontext` function specifically expects `int` arguments. What makes it safe to pass argument types other than `int`? I am aware that on most ABIs they are probably passed the same way. – user253751 Feb 22 '23 at 19:41
  • My apologies, @Jack -- I have had to make a major modification to this answer after your acceptance. The whole sense of it has changed. – John Bollinger Feb 22 '23 at 19:57
  • Damn, @user253751, that's a good question. Answer revised. – John Bollinger Feb 22 '23 at 19:59
  • @JohnBollinger I see... Can I do something like void thread_routine_handler(int a, int b, intc c) then do something like `thread_t *th = (thread_t *) a;` etc? – Jack Feb 22 '23 at 20:33
  • No, @Jack, it is not safe to rely on conveying pointers by converting them to type `int`. It might *happen* to work on machines where `int` and all pointer types, including function pointers, have the same size, but there are lots of machines where that is not the case. Most 64-bit machines, for example, and, historically, most machines with 16-bit `int`s. – John Bollinger Feb 22 '23 at 20:39
  • @JohnBollinger any other way to pass pointer to this function without using globals? – Jack Feb 22 '23 at 20:46
  • @JohnBollinger so void thread_routine_handler(int index) { } calling them like makecontext(&th->context, (start_routine_t) thread_routine_handler, 1, myIndex); is the way to go then, right? – Jack Feb 22 '23 at 20:51
  • That's what I recommend, @Jack. In principle, you can encode arbitrary information, including pointer values, into a long enough sequence of integers (and probably two per pointer would be enough here), but that's ugly and error prone. If you use "globals" with internal linkage (*i.e.*, declare them `static` at file scope) and therefore use them only in functions defined in the same file, then I don't think you need to be too put off. – John Bollinger Feb 22 '23 at 20:55
  • @JohnBollinger i see, i'll use a global with static then. thanks! – Jack Feb 22 '23 at 21:09