You could take a look at the codebase for Valgrind as a start for monitoring the threads stack.
You should be able to free the malloc'd stack memory on pthread_exit, set the stack pointer to NULL and reuse.
Code:
/*header defs*/
typedef struct custom_thread {
int cr_return;
int thread_stack_size;
pthread_t tid;
pthread_attr_t attr;
void *newstack;
} CTHREAD;
extern CTHREAD *init_custom_thread(int, int (*)(void *));
extern void destroy_custom_thread(CTHREAD *);
/*from shared library*/
extern CTHREAD *init_custom_thread(int sz, int (*teststackalloc)(void *arg)) {
CTHREAD *ret;
if ( (ret = malloc(sizeof(*ret))) == NULL) {return NULL;}
ret->thread_stack_size = sz;
if ( (ret->newstack = malloc(sz)) == NULL) {free(ret); return NULL;}
if (teststackalloc!= NULL) {
if (teststackalloc(ret->newstack) != 0) {free(ret->newstack); free(ret); return NULL;}
}
pthread_attr_init(&ret->attr);
ret->cr_return = pthread_attr_setstack(&ret->attr,ret->newstack,ret->thread_stack_size);
return ret;
}
extern void destroy_custom_thread(CTHREAD *arg) {
if (arg->newstack != NULL) {free(arg->newstack); arg->newstack = NULL;}
free(arg);
arg = NULL;
}
/*test program*/
#include "custom_stack.h"
#include <limits.h>
void *hello_world(void *arg) {
printf("Hello world from thread id %d.\n",pthread_self());
pthread_exit(NULL);
}
void dothread_work(CTHREAD *new_id) {
pthread_create(&new_id->tid,&new_id->attr,hello_world,NULL);
pthread_join(new_id->tid,NULL);
destroy_custom_thread(new_id);
}
int main(void) {
int i = 0;
CTHREAD *new_id;
new_id = init_custom_thread(PTHREAD_STACK_MIN * 2, NULL);
if (new_id == NULL) {return 1;}
for (i = 0 ; i < 100; i++) {dothread_work(new_id); new_id = init_custom_thread(PTHREAD_STACK_MIN * 2, NULL);}
dothread_work(new_id);
return 0;
}
All works as expected.