C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
119: Checked Integer Arithmetic with stdckdint.h (C23)
Integer overflow is one of those bugs that feels like a ghost in the machine. You write a simple multiplication, it works for a thousand tests, and then—in production—a user inputs a massive number, the result wraps around to a tiny positive integer, you allocate a buffer that's too small, and suddenly you've handed a hacker a heap overflow vulnerability. For decades, we've had to write clunky, manual checks to avoid this, but C23 finally gives us stdckdint.h.
The Danger of Naive Multiplication
I want to show you this using a common scenario: calculating the total size for a dynamic array of structures. Let's say we're building a small system to track sensor readings. We have a Reading struct, and we need to allocate memory for n of them.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
uint64_t timestamp;
double value;
uint32_t sensor_id;
} Reading;
size_t calculate_buffer_size(size_t count) {
// Naive approach: just multiply.
// If count is massive, this wraps around.
return count * sizeof(Reading);
}
If count is large enough, that multiplication will overflow size_t. The function will return a small number, malloc will succeed in allocating that small amount, and as soon as we start writing our Reading objects into that buffer, we're corrupting memory. I've seen this exact bug crash systems in the wild more times than I care to admit.
Catching the Overflow with C23 Macros
In C23, we can use ckd_mul. These macros are great because they are designed to be optimized by the compiler into the most efficient CPU flags check available (like the Carry or Overflow flags on x86). Here is how I'll refactor this to be safe.
#include <stdio.h>
#include <stdckdint.h>
#include <stdbool.h>
bool try_calculate_buffer_size(size_t count, size_t *out_size) {
// ckd_mul returns true if overflow occurs, false otherwise.
if (ckd_mul(&*out_size, count, sizeof(Reading))) {
return false; // Overflow happened!
}
return true; // Success, result is in out_size
}
Notice that ckd_mul takes a pointer to where the result should be stored. This separation of the "success" boolean and the "result" value is key to making the logic explicit.
A Quick Detour: My Mistake with Pointer Syntax
When I first started playing with these macros, I tried to be "clever" and pass the result pointer directly from a calling function. I wrote something like this:
size_t total;
if (ckd_mul(&total, count, size)) { ... }
But then I tried to wrap it in a helper and accidentally passed a size_t * directly into the macro, thinking it would just work. I ended up with a compiler error because I was essentially passing a size_t ** to the macro. It reminded me that while these look like functions, they are macros. They expect a pointer to the variable where the result goes. If you're already inside a function that received a pointer as an argument (like out_size in my previous example), you have to dereference it first (&*out_size) or just pass the pointer if the types match perfectly. In my case, out_size was already a size_t *, so I actually didn't need the &*—I just overthought it. I'll clean that up in the final version below.
Putting it all together
Let's look at the polished version. I'll add ckd_add as well, because usually, you don't just allocate the array; you might add some extra padding for a header or a null terminator.
#include <stdio.h>
#include <stdckdint.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct {
uint64_t timestamp;
double value;
} Reading;
bool safe_alloc_size(size_t count, size_t padding, size_t *result) {
size_t base_size;
// Check multiplication for the array
if (ckd_mul(&base_size, count, sizeof(Reading))) {
printf("Error: Multiplication overflow\n");
return false;
}
// Check addition for the padding
if (ckd_add(result, base_size, padding)) {
printf("Error: Addition overflow\n");
return false;
}
return true;
}
int main() {
size_t final_size;
// This count is intentionally absurd to trigger the overflow
size_t huge_count = (size_t)-1 / sizeof(Reading);
if (safe_alloc_size(huge_count, 1024, &final_size)) {
printf("Allocating %zu bytes\n", final_size);
} else {
printf("Allocation failed safely.\n");
}
return 0;
}
By using stdckdint.h, we've turned a potential security vulnerability into a simple, handleable error. It's cleaner than the old way of doing if (count > SIZE_MAX / sizeof(Reading)), and it's much harder to mess up.
📋 Practical Task
Exercise: Building a Safe Virtual Wallet
You are writing a module for a virtual wallet. The wallet handles credits as uint32_t. You need to implement a function that processes a "bundle" purchase. The user buys quantity items, each costing unit_price, and there is a fixed transaction_fee.
Your Task: Write a function bool process_purchase(uint32_t quantity, uint32_t unit_price, uint32_t transaction_fee, uint32_t *total_cost) that uses stdckdint.h to:
- Calculate the subtotal (
quantity * unit_price). - Add the
transaction_feeto that subtotal. - Return
falseif any overflow occurs at any step, andtrue(with the result intotal_cost) if the calculation is safe.
Test your function with a case that would overflow a 32-bit integer (e.g., quantity = 1,000,000 and unit_price = 5,000) to ensure your safety checks are working.
There are no comments for now.