Question
How can I determine how many CPU cores a machine has from C or C++ code in a platform-independent way?
If there is no fully portable solution, what are the common platform-specific ways to detect the number of cores on:
- Windows
- Unix/Linux
- macOS
A practical answer should clarify the difference between logical processors and physical CPU cores, since that affects what a program may want to measure.
Short Answer
By the end of this page, you will understand how C and C++ programs can detect available CPU parallelism, what “core count” really means, and which APIs are commonly used on Windows, Linux/Unix, and macOS. You will also learn the difference between physical cores and logical processors, when a portable answer is possible, and how to write simple fallback code.
Concept
When developers ask for the “number of cores,” they often mean one of several different things:
- Logical processors: the number of hardware execution units the OS exposes to your program
- Physical cores: actual CPU cores on the chip
- Available CPUs to this process: how many processors the OS currently allows your process to use
These are not always the same.
For example, a machine with:
- 1 CPU package
- 4 physical cores
- Hyper-Threading enabled
may appear as 8 logical processors to the operating system.
Why this matters
Programs often use this information to:
- choose a thread pool size
- parallelize computation
- limit background work
- tune performance settings
But there is an important detail:
- If you want to know how much parallel work your program can likely run, logical processor count is usually the most useful number.
- If you want hardware topology details, such as true physical core count, the solution becomes more platform-specific and sometimes more complex.
Is there a platform-independent way?
In modern C++
Yes, for logical concurrency, C++ provides:
#include <thread>
unsigned int n = std::thread::hardware_concurrency();
This is the closest thing to a portable answer in standard C++.
However:
- it returns a hint, not a guarantee
- it usually reports logical processors, not physical cores
- it may return
0if the value cannot be determined
In C
Standard C does not provide a portable API for CPU core count. You usually need platform-specific APIs such as:
GetSystemInfoorGetLogicalProcessorInformationon Windowssysconf(_SC_NPROCESSORS_ONLN)on many Unix/Linux systemssysctlon macOS and BSD systems
Core idea
There is no single standard C API for this. There is a practical standard C++ answer for concurrency hints. For exact hardware details, you often need per-platform code.
Mental Model
Think of the CPU like a workplace.
- Physical cores are the actual workers.
- Logical processors are the desks the manager says can be used at once.
- Available CPUs to your process are the desks currently assigned to your team.
If a worker can juggle two tasks with technologies like Hyper-Threading, the office may report more usable desks than actual workers.
So when your program asks, “How many cores are there?”, the real question is often:
- How many workers exist?
- How many desks are visible?
- How many desks can my program use right now?
That is why this question has different answers depending on what exactly you want to measure.
Syntax and Examples
Portable C++ option
If you are using modern C++, the simplest portable approach is:
#include <iostream>
#include <thread>
int main() {
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) {
std::cout << "Could not detect hardware concurrency.\n";
} else {
std::cout << "Logical processors available: " << n << "\n";
}
return 0;
}
What this does
- Includes
<thread> - Calls
std::thread::hardware_concurrency() - Prints the number of logical processors suggested by the implementation
This is usually the best first choice in portable C++.
Unix/Linux example
A common C or C++ approach on Unix-like systems is sysconf:
#include <iostream>
#include <unistd.h>
int main() {
long n = sysconf(_SC_NPROCESSORS_ONLN);
if (n < 1) {
std::cout << "Could not detect processor count.\n";
} else {
std::cout << "Online logical processors: " << n << "\n";
}
return 0;
}
Notes
_SC_NPROCESSORS_ONLNmeans processors currently online- some systems also support
_SC_NPROCESSORS_CONFfor configured processors
Windows example
#include <iostream>
#include <windows.h>
int main() {
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
std::cout << "Logical processors: "
<< sysInfo.dwNumberOfProcessors << "\n";
return 0;
}
Notes
dwNumberOfProcessorsgives the number of logical processors visible to the process environment- for deeper topology, Windows has more advanced APIs such as
GetLogicalProcessorInformation
macOS example
#include <iostream>
#include <sys/types.h>
#include <sys/sysctl.h>
int main() {
int nm[2];
size_t len = 4;
uint32_t count;
nm[0] = CTL_HW;
nm[1] = HW_AVAILCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
if (count < 1) {
nm[1] = HW_NCPU;
sysctl(nm, 2, &count, &len, NULL, 0);
}
std::cout << "Available logical processors: " << count << "\n";
return 0;
}
Notes
HW_AVAILCPUasks for available CPUsHW_NCPUis a fallback for total CPUs- on Apple platforms, exact physical-vs-logical details may require other
sysctlkeys depending on the system version
Step by Step Execution
Consider this C++ example:
#include <iostream>
#include <thread>
int main() {
unsigned int count = std::thread::hardware_concurrency();
if (count == 0) {
std::cout << "Unknown\n";
} else {
std::cout << count << "\n";
}
}
Step by step
1. Include headers
#include <iostream>
#include <thread>
<iostream>lets us print output<thread>provideshardware_concurrency()
2. Ask the implementation for hardware concurrency
unsigned int count = std::thread::hardware_concurrency();
The C++ runtime asks the operating system or platform layer for a concurrency hint.
Possible results:
8on an 8-thread machine16on a CPU with 8 physical cores and 16 logical threads0if the value cannot be determined
3. Handle the unknown case
if (count == 0) {
std::cout << "Unknown\n";
}
This matters because the standard does not guarantee a nonzero result.
4. Print the count if known
else {
std::cout << count << "\n";
}
If detection succeeds, the program prints the value.
Example trace
Suppose the machine exposes 12 logical processors.
hardware_concurrency()returns12count == 0is false- the program prints
12
Suppose detection fails.
hardware_concurrency()returns0count == 0is true- the program prints
Unknown
Real World Use Cases
Thread pool sizing
A server or desktop application may choose a default thread pool size based on available processors.
unsigned int workers = std::thread::hardware_concurrency();
if (workers == 0) workers = 4;
Parallel data processing
A program that compresses files, processes images, or runs simulations can divide work across CPUs.
Examples:
- image resizing pipeline
- scientific calculations
- build systems compiling multiple files at once
Game engines and media tools
Games and creative tools often split work into systems such as:
- rendering preparation
- physics
- audio processing
- asset streaming
Knowing available concurrency helps choose a sensible job system size.
CLI tools and batch scripts
Command-line tools often support flags like:
mytool --jobs 8
If the user does not provide a value, the tool may detect available processors and pick a default.
Containers and restricted environments
In production, a process may not be allowed to use all system CPUs. That means the useful number may be the CPUs available to the process, not the machine's total hardware.
Real Codebase Usage
In real projects, developers usually do not blindly create one thread per core forever. Instead, they use processor count as a starting point.
Common patterns
Guard clause with fallback
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) n = 4;
This is common because detection may fail.
Cap resource usage
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) n = 4;
if (n > 8) n = 8;
A codebase may avoid using every processor to keep the system responsive.
Reserve one core for the OS
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) n = 4;
if (n > 1) --n;
Some applications intentionally leave one logical processor free.
User-configurable override
Real applications often let users override automatic detection through:
- config files
- command-line options
- environment variables
That is helpful because automatic detection is not always the best tuning choice.
Platform abstraction layer
Larger codebases wrap this logic in one function:
unsigned int get_worker_count();
Internally, that function may:
- try standard C++ first
- fall back to platform-specific APIs
- apply project-specific rules
This keeps the rest of the code portable and clean.
Common Mistakes
Mistake 1: Assuming “core count” always means physical cores
Many APIs return logical processors, not physical cores.
Broken assumption:
unsigned int cores = std::thread::hardware_concurrency();
This variable name may be misleading. A better name is:
unsigned int logical_processors = std::thread::hardware_concurrency();
Mistake 2: Not handling a return value of 0
Broken code:
unsigned int n = std::thread::hardware_concurrency();
std::vector<int> data(n);
If n is 0, your logic may break or create useless behavior.
Better:
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) n = 4;
Mistake 3: Creating too many threads
Beginners sometimes think more threads always means more speed.
Broken idea:
for (int i = 0; i < 1000; ++i) {
// create a thread
}
Too many threads can cause:
- context switching overhead
- memory waste
- worse performance
Use a thread pool or bounded number of workers.
Mistake 4: Ignoring platform differences
Code like this is not portable:
#include <windows.h>
If you need portability, isolate OS-specific code behind #ifdef blocks or use standard C++ where possible.
Mistake 5: Confusing online CPUs with configured CPUs
On Unix-like systems:
_SC_NPROCESSORS_CONFcan mean configured processors_SC_NPROCESSORS_ONLNmeans currently online processors
If you want usable processors right now, use the online value.
Mistake 6: Treating detected count as a perfect tuning answer
The detected number is just a hint. Actual performance depends on:
- workload type
- I/O vs CPU-bound work
- memory bandwidth
- process limits
- system load
Comparisons
| Approach | Language | Portable? | Usually Returns | Notes |
|---|---|---|---|---|
std::thread::hardware_concurrency() | C++ | Yes | Logical processors hint | May return 0 |
sysconf(_SC_NPROCESSORS_ONLN) | C/C++ on Unix/Linux | No | Online logical processors | Common POSIX-style solution |
GetSystemInfo() | C/C++ on Windows | No | Logical processors | Simple Windows API |
sysctl() | C/C++ on macOS/BSD | No | Available or total processors | Key names vary by platform |
Logical processors vs physical cores
| Measure | Meaning | Typical use |
|---|---|---|
| Logical processors | Execution contexts visible to OS | Thread pool sizing |
| Physical cores | Actual hardware cores | Hardware reporting, topology analysis |
| Available CPUs to process | CPUs your program can use now | Production tuning, containers, affinity-aware apps |
Portable vs platform-specific
| Option | Best when | Tradeoff |
|---|---|---|
| Standard C++ | You want one code path | Less hardware detail |
| Platform-specific APIs | You need exact OS-specific information | More code and maintenance |
| Abstraction wrapper | You want both | Slightly more design work upfront |
Cheat Sheet
Quick reference
Standard C++
#include <thread>
unsigned int n = std::thread::hardware_concurrency();
- portable in C++
- usually returns logical processors
- may return
0
Unix/Linux
#include <unistd.h>
long n = sysconf(_SC_NPROCESSORS_ONLN);
- returns online logical processors
- check for values less than
1
Windows
SYSTEM_INFO info;
GetSystemInfo(&info);
DWORD n = info.dwNumberOfProcessors;
- returns logical processors
macOS/BSD
sysctl(... HW_AVAILCPU ...)
sysctl(... HW_NCPU ...)
- available CPU count, then fallback to total
Rules to remember
- “core count” may mean different things
- standard C has no portable API for this
- standard C++ has
hardware_concurrency() - logical processors are usually more relevant for parallel code
- always handle unknown results or fallback cases
Safe default pattern
unsigned int n = std::thread::hardware_concurrency();
if (n == 0) n = 4;
Best practice
- prefer portable C++ if possible
- wrap platform-specific code in one helper function
- allow user override for performance-sensitive programs
FAQ
Does C have a standard function to get the number of CPU cores?
No. Standard C does not define a portable CPU-count API. You usually need platform-specific system calls.
Is std::thread::hardware_concurrency() the number of physical cores?
Usually no. It commonly reports logical processors, not guaranteed physical cores.
Why can std::thread::hardware_concurrency() return 0?
Because the C++ standard defines it as a hint. If the implementation cannot determine the value, it may return 0.
What is the difference between logical processors and physical cores?
Physical cores are actual hardware cores. Logical processors are execution units visible to the OS, which may be higher due to technologies like Hyper-Threading.
Which count should I use for thread pools?
Usually logical processor count is a practical starting point, then adjust based on workload and testing.
Is there one API that works on Windows, Linux, and macOS in C?
No standard C API exists for all of them. You usually write platform-specific code with conditional compilation.
Should I always create one thread per core?
Not always. Too many threads can reduce performance. Thread pools and bounded worker counts are usually better.
Can CPU affinity or containers affect the result?
Yes. A machine may have many CPUs, but your process may only be allowed to use some of them.
Mini Project
Description
Build a small cross-platform C++ utility function that reports the machine's available processor count. This project demonstrates how to prefer a portable standard C++ solution first, then apply a fallback, and finally use the result in a realistic thread-count decision.
Goal
Create a C++ program that detects available logical processors and chooses a safe worker-thread count.
Requirements
- Write a function that returns the detected logical processor count.
- Use
std::thread::hardware_concurrency()as the primary method. - Fall back to a reasonable default if detection fails.
- Print both the detected count and the chosen worker count.
- Leave one processor free when more than one is available.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.