Chapter 33 · Section 33.1
Thread Stacks
Default sizes, limits, resizing, and the virtual address space calculation
Key Terms
What Is a Thread Stack?
Every thread needs its own private stack. The stack holds:
- Local variables declared inside functions
- Function call return addresses
- Function arguments (depending on architecture calling convention)
- Saved registers during context switches
Because each thread runs independently, they must each have their own stack. If two threads shared a stack, their local variables would collide and corrupt each other.
Virtual Address Space Layout — x86-32 Process with 3 Threads
On x86-32, user space = 3 GB. Each non-main thread stack = 2 MB → max ~1500 threads.
Default Stack Sizes
The default per-thread stack size (for threads other than the main thread) depends on the architecture:
| Architecture | Default Stack Size | Note |
|---|---|---|
| x86-32 (i386) | 2 MB | Most common embedded reference |
| x86-64 | 8 MB (glibc default) | Larger due to 64-bit address space |
| IA-64 (Itanium) | 32 MB | Very large due to register stack engine |
| ARM (32-bit) | 2 MB (typical) | Common in embedded systems |
RLIMIT_STACK). For non-main threads, the threading library must pre-allocate the entire stack up front using mmap(), because it must pick a fixed location in the virtual address space.On x86-32 with a 3 GB user virtual address space:
(actual limit is lower due to text/data/heap/shared libs consuming some space)
Querying the Minimum Stack Size
You can query the minimum stack size supported by the current threading implementation using sysconf():
#include <unistd.h> /* sysconf */
#include <stdio.h>
int main(void)
{
long min_stack = sysconf(_SC_THREAD_STACK_MIN);
printf("Minimum thread stack size: %ld bytes (%ld KB)\n",
min_stack, min_stack / 1024);
return 0;
}
/* On NPTL / x86-32: prints 16384 bytes = 16 KB */
sysconf(_SC_THREAD_STACK_MIN). The threading library itself needs some stack space for internal bookkeeping.Resizing Thread Stacks
Use pthread_attr_setstacksize() to set the stack size in a thread attributes object before creating the thread.
| Function | Purpose |
|---|---|
pthread_attr_setstacksize(attr, size) |
Set stack size (bytes) |
pthread_attr_getstacksize(attr, &size) |
Get current stack size setting |
pthread_attr_setstack(attr, stackaddr, size) |
Set both address AND size (reduces portability) |
Code Example 1 — Set Custom Stack Size
This example creates a thread with a reduced 512 KB stack. Useful for embedded systems or when you need many threads in a constrained address space.
/*
* ep_stack_size.c — Create a thread with a custom stack size
* Compile: gcc -o ep_stack_size ep_stack_size.c -lpthread
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define STACK_SIZE (512 * 1024) /* 512 KB custom stack */
void *thread_func(void *arg)
{
/* Local array to demonstrate stack usage */
char local_buf[1024];
local_buf[0] = 'A'; /* touch the memory */
printf("[Thread %lu] Running with custom 512 KB stack\n",
(unsigned long)pthread_self());
printf("[Thread %lu] local_buf is at %p\n",
(unsigned long)pthread_self(), (void *)local_buf);
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
size_t stack_size;
int ret;
/* Step 1: Initialise attributes object */
ret = pthread_attr_init(&attr);
if (ret != 0) { perror("pthread_attr_init"); exit(EXIT_FAILURE); }
/* Step 2: Set the stack size */
ret = pthread_attr_setstacksize(&attr, STACK_SIZE);
if (ret != 0) { perror("pthread_attr_setstacksize"); exit(EXIT_FAILURE); }
/* Step 3: Verify what was set */
pthread_attr_getstacksize(&attr, &stack_size);
printf("[Main] Thread stack size will be: %zu KB\n", stack_size / 1024);
/* Step 4: Create thread with the attribute */
ret = pthread_create(&tid, &attr, thread_func, NULL);
if (ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); }
/* Step 5: Destroy attributes (no longer needed after create) */
pthread_attr_destroy(&attr);
pthread_join(tid, NULL);
printf("[Main] Thread finished. Program exiting.\n");
return 0;
}
Expected output:
[Main] Thread stack size will be: 512 KB
[Thread 140234567890] Running with custom 512 KB stack
[Thread 140234567890] local_buf is at 0x7f3a40007a00
[Main] Thread finished. Program exiting.
Code Example 2 — RLIMIT_STACK and Many Threads
This example demonstrates querying stack limits, checking RLIMIT_STACK, and estimating the theoretical maximum thread count based on virtual address space.
/*
* ep_stack_limits.c — Explore stack size limits
* Compile: gcc -o ep_stack_limits ep_stack_limits.c -lpthread
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
int main(void)
{
struct rlimit rl;
long min_stack;
size_t default_stack;
pthread_attr_t attr;
/* Query minimum stack size for this implementation */
min_stack = sysconf(_SC_THREAD_STACK_MIN);
printf("sysconf(_SC_THREAD_STACK_MIN) = %ld bytes (%ld KB)\n",
min_stack, min_stack / 1024);
/* Query RLIMIT_STACK — affects NPTL default stack size */
if (getrlimit(RLIMIT_STACK, &rl) == 0) {
if (rl.rlim_cur == RLIM_INFINITY)
printf("RLIMIT_STACK = unlimited\n");
else
printf("RLIMIT_STACK = %lu KB\n",
(unsigned long)rl.rlim_cur / 1024);
}
/* Get the default stack size from a default attr object */
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &default_stack);
printf("Default thread stack size (attr) = %zu KB\n",
default_stack / 1024);
pthread_attr_destroy(&attr);
/* Calculate theoretical max threads on x86-32 (3 GB user space) */
unsigned long user_space = 3UL * 1024 * 1024 * 1024; /* 3 GB */
unsigned long max_threads = user_space / default_stack;
printf("\nTheoretical max threads (x86-32, 3GB user space): ~%lu\n",
max_threads);
printf("(Real limit lower due to text/data/heap/libs overhead)\n");
/* Show how reducing stack size increases max thread count */
printf("\nWith 256 KB stack: ~%lu threads\n",
user_space / (256 * 1024));
printf("With 512 KB stack: ~%lu threads\n",
user_space / (512 * 1024));
printf("With 2048 KB stack: ~%lu threads\n",
user_space / (2048 * 1024));
return 0;
}
/* NOTE: Under NPTL, if RLIMIT_STACK != unlimited, it becomes
* the default stack size for new threads. Set it BEFORE exec()
* using: ulimit -s 512 (in shell, before running the program)
* Calling setrlimit() inside main() is too late for NPTL. */
RLIMIT_STACK becomes the default thread stack size. But NPTL reads it during run-time initialisation — before main() is even called. You must set it using ulimit -s in the shell before running the program. Calling setrlimit() inside main() has no effect on the default stack size.When Should You Change the Stack Size?
✅ Increase Stack When…
- Thread uses large local arrays (e.g.,
char buf[1MB]) - Deep recursive function calls
- C++ exception handling overhead
- Threads calling alloca() with large sizes
✅ Decrease Stack When…
- You need many threads (hundreds/thousands)
- Threads are simple worker threads with few locals
- Running on a constrained virtual address space (32-bit)
- Embedded targets with limited RAM
Interview Questions
RLIMIT_STACK. Non-main threads cannot grow dynamically because the threading library must pre-allocate the entire stack at a fixed virtual address using mmap().pthread_attr_setstacksize().RLIMIT_STACK is set to anything other than RLIM_INFINITY, it is used as the default stack size for all newly created threads. This is read during the C library’s run-time initialisation, which happens before main() is called. Therefore, calling setrlimit(RLIMIT_STACK, ...) inside main() has no effect on default stack size. The correct way to set it is via ulimit -s <kbytes> in the shell before executing the program.pthread_attr_setstacksize() sets only the size of the stack — the threading library chooses the location automatically. pthread_attr_setstack() sets both the address and the size of the stack, giving you full control. Setting the address manually reduces portability (the address must be valid, aligned, and sized correctly for the target architecture) and is rarely needed outside embedded or real-time systems where you manage memory explicitly.mmap() region. Depending on whether a guard page is present (NPTL enables a guard page by default via pthread_attr_setguardsize()), the overflow may be detected cleanly or corrupt adjacent memory.sysconf(_SC_THREAD_STACK_MIN). On NPTL/x86-32, this returns 16,384 bytes (16 KB). You should never set a thread’s stack smaller than this value. The minimum exists because the Pthreads library itself needs some stack space for its internal book-keeping when the thread starts up.Section Summary
- Each thread (except main) gets a fixed-size private stack, default 2 MB on x86-32.
- Use pthread_attr_setstacksize() before pthread_create() to resize.
- Minimum stack size: query with sysconf(_SC_THREAD_STACK_MIN) → 16 KB on NPTL.
- On 32-bit systems, 2 MB stacks limit you to ~1500 threads; reduce stack to increase count.
- NPTL reads RLIMIT_STACK before
main(); set it via shell, not setrlimit() inside program.
Keep Learning — It’s Free
EmbeddedPathashala covers Linux, BLE, RTOS, and more for students and freshers.
