Dass sich die Adressen von dem gepushten task und dem "extrahierten" unterscheiden, ist doch klar: Du allokierst vor dem extrahieren neuen Speicher mit calloc und übergibst diesen an die Funktion q_extract_head.
Zunächst mal kann das so nicht funktionieren, da in der Funktion q_extract_head sizeof(void) genommen wird. Mich wundert, dass der Compiler das überhaupt zulässt. Jedenfalls wird dieser sizeof nicht das zurückgeben, was Du haben möchtest, und somit nicht den kompletten Task aus der Queue nach data kopieren. Warum hier überhaupt neuen Speicher allokieren?
Wenn sichergestellt ist, dass der Task nicht von einem anderen Thread zwischen q_peek und q_pop gelöscht wird, einfach:
task_t *task_p = q_peek_head(queue);
// ...
q_pop_head(queue);
Wenn wirklich eine Kopie nötig ist, musst Du die Größe an q_extract_head mit übergeben. Dann wunder Dich aber auch nicht, dass die Adresse des neu allokierten und kopierten Tasks anders ist
Wenn (wie ich vermute) immer nur ein Thread an einem Ende der Queue löscht und ein anderer am anderen Ende einfügt, schmeiss die q_extract Funktionen besser ganz weg.
Abgesehen davon, dass q_extract "broken" ist, prüfst Du nicht (wie in dem #else Zweig), ob die Queue überhaupt Daten enthält. Du forderst mit calloc Speicher für task_p an, beschreibst diesen in q_extract (wobei diese Funktion zurückkehrt, wenn task_p NULL ist, also calloc fehlgeschlagen ist) und arbeitest dann mit task_p weiter, ungeachtet dessen, ob q_extract überhaupt ein Element kopieren konnte. Das data = NULL in q_extract bewirkt nicht, wie Du vielleicht glaubst, dass task_p nach dem Aufruf NULL ist. Änderungen an lokalen Variablen sind beim Aufrufer nicht sichtbar, da in C alles call-by-value ist. Entweder Du übergibst einen Zeiger auf den Zeiger, damit q_extract den Zeiger selbst ändern kann, oder Du zeigst den Erfolg der Funktion mit einem Rückgabewert an (wobei ich da die zweitere Variante bevorzugen würde).
Zum Schluss noch ein kleiner Hinweis auf etwas, was ich als schlechten Stil bezeichnen würde: Den Speicher für data allokiert bei dieser Queue der Aufrufer, freigeben tut aber die Queue (in q_node_free). Eigentlich sollte sich um die Freigabe immer derjenige kümmern, der den Speicher auch angefordert hat. So nimmst Du Dir die Möglichkeit, jemals etwas in die Queue zu packen, was nicht mit malloc/calloc allokiert wurde.