A modern C11 library designed to make programming in C less painful and more productive, written in pure C. MisraStdC provides generic containers, string handling, and formatted I/O inspired by higher-level languages while maintaining C's performance and control.
Disclaimer: This library is not related to the MISRA C standard or guidelines. The name "MisraStdC" comes from the author's name, Siddharth Mishra, who is commonly known as "Misra" among friends.
- Cross-platform compatibility: Supports MSVC, GCC, and Clang
- Type-safe generic containers:
Vec(T)
: Generic vector with strict type checkingStr
: String handling (specializedVec(char)
)Map(K, V)
: Generic key-value hash-map storage (WIP)Int
: Custom big integer implementation (WIP)
- Rust-style formatted I/O:
WriteFmt
,ReadFmt
: Type-safe formatted standard I/OStrWriteFmt
,StrReadFmt
: Type-safe formatted string operations
- JSON parsing and serialization
- Memory safety with proper initialization and cleanup functions
-
C11 compatible compiler:
- GCC: Version 4.9 or newer
- Clang: Version 3.4 or newer
- MSVC: Visual Studio 2019 or newer
-
Build System:
# Clone the repository with submodules
git clone --recursive https://github.com/brightprogrammer/MisraStdC.git
cd MisraStdC
# Configure the build
meson setup builddir
# Build the library
ninja -C builddir
# Run tests
ninja -C builddir test
For development with sanitizers (recommended for debugging):
meson setup builddir -Db_sanitize=address,undefined -Db_lundef=false
Comprehensive API documentation is available at docs.brightprogrammer.in.
#include <Misra.h>
int compare_ints(const void* a, const void* b) {
return *(const int*)a - *(const int*)b;
}
int main() {
// Initialize vector with default alignment
Vec(int) numbers = VecInit();
// Pre-allocate space for better performance
VecReserve(&numbers, 10);
// Insert elements (ownership transfer for l-values)
int val = 42;
VecInsertL(&numbers, &val, 0); // val is now owned by vector
VecInsertR(&numbers, 10, 0); // Insert at front
VecInsertR(&numbers, 30, 1); // Insert in middle
// Access elements safely
int first = VecAt(&numbers, 0); // Get by value
int* first_ptr = VecPtrAt(&numbers, 0); // Get by pointer
int last = VecLast(&numbers); // Last element
// Batch operations
int items[] = {15, 25, 35};
VecInsertRange(&numbers, items, VecLen(&numbers), 3);
// Sort the vector
VecSort(&numbers, compare_ints);
// Different iteration patterns
VecForeachIdx(&numbers, val, idx, {
printf("[%zu] = %d\n", idx, val);
});
// Modify elements in-place
VecForeachPtr(&numbers, ptr, {
*ptr *= 2;
});
// Memory management
VecTryReduceSpace(&numbers); // Optimize memory usage
size_t size = VecSize(&numbers); // Size in bytes
// Batch removal
VecDeleteRange(&numbers, 1, 2); // Remove 2 elements starting at index 1
// Clear all elements but keep capacity
VecClear(&numbers);
// Final cleanup
VecDeinit(&numbers);
}
#include <Misra.h>
int main() {
// Strings are just Vec(char) with special operations
Str text = StrInit();
// String creation
Str hello = StrInitFromZstr("Hello");
Str world = StrInitFromCstr(", World!", 8);
// Formatted append
StrAppendf(&text, "%.*s%.*s\n",
(int)hello.length, hello.data,
(int)world.length, world.data);
// String operations
bool starts = StrStartsWithZstr(&text, "Hello");
bool ends = StrEndsWithZstr(&text, "!\n");
// Split into vector of strings
Str csv = StrInitFromZstr("one,two,three");
Strs parts = StrSplit(&csv, ",");
// Process split results
VecForeachPtr(&parts, str, {
printf("Part: %.*s\n", (int)str->length, str->data);
});
// Cleanup
StrDeinit(&text);
StrDeinit(&hello);
StrDeinit(&world);
StrDeinit(&csv);
// Cleanup split results
VecForeachPtr(&parts, str, {
StrDeinit(str);
});
VecDeinit(&parts);
}
#include <Misra.h>
int main() {
// String formatting
Str output = StrInit();
// Basic formatting with direct values
int count = 42;
const char* name = "Test";
StrWriteFmt(&output, "Count: {}, Name: {}\n", count, name); // Pass values directly, not pointers
// Format with alignment and hex
u32 hex_val = 0xDEADBEEF;
StrWriteFmt(&output, "Hex: {X}\n", hex_val);
// Read formatted input
const char* input = "Count: 42, Name: Test";
int read_count;
Str read_name = StrInit();
// For reading, we pass the variables directly
StrReadFmt(input, "Count: {}, Name: {}", read_count, read_name); // No & operator needed
// Multiple value types
float pi = 3.14159f;
u64 big_num = 123456789ULL;
StrWriteFmt(&output, "Float: {.2f}, Integer: {}, Hex: {x}\n", pi, big_num, big_num);
// String formatting
Str hello = StrInitFromZstr("Hello");
StrWriteFmt(&output, "String: {}\n", hello); // Pass Str directly
// Cleanup
StrDeinit(&output);
StrDeinit(&read_name);
StrDeinit(&hello);
}
#include <Misra.h>
// Define our data structures
typedef struct Point {
float x;
float y;
} Point;
typedef struct Shape {
Str name;
Point position;
Vec(Point) vertices;
bool filled;
} Shape;
int main() {
// Example JSON string
Str json = StrInitFromZstr(
"{"
" \"name\": \"polygon\","
" \"position\": {\"x\": 10.5, \"y\": 20.0},"
" \"vertices\": ["
" {\"x\": 0.0, \"y\": 0.0},"
" {\"x\": 10.0, \"y\": 0.0},"
" {\"x\": 5.0, \"y\": 10.0}"
" ],"
" \"filled\": true"
"}"
);
// Create our shape object
Shape shape = {
.name = StrInit(),
.vertices = VecInit()
};
// Parse JSON into our structure
StrIter si = StrIterFromStr(&json);
JR_OBJ(si, {
// Read string value with key "name"
JR_STR_KV(si, "name", shape.name);
// Read nested object with key "position"
JR_OBJ_KV(si, "position", {
JR_FLT_KV(si, "x", shape.position.x);
JR_FLT_KV(si, "y", shape.position.y);
});
// Read array of objects with key "vertices"
JR_ARR_KV(si, "vertices", {
Point vertex = {0};
JR_OBJ(si, {
JR_FLT_KV(si, "x", vertex.x);
JR_FLT_KV(si, "y", vertex.y);
});
VecInsertR(&shape.vertices, vertex, VecLen(&shape.vertices));
});
// Read boolean value with key "filled"
JR_BOOL_KV(si, "filled", shape.filled);
});
// Modify some values
shape.position.x += 5.0;
VecForeachPtr(&shape.vertices, vertex, {
vertex->y += 1.0; // Move all points up by 1
});
// Write back to JSON
StrClear(&json); // Clear existing content
JW_OBJ(json, {
// Write string key-value
JW_STR_KV(json, "name", shape.name);
// Write nested object
JW_OBJ_KV(json, "position", {
JW_FLT_KV(json, "x", shape.position.x);
JW_FLT_KV(json, "y", shape.position.y);
});
// Write array of objects
JW_ARR_KV(json, "vertices", shape.vertices, vertex, {
JW_OBJ(json, {
JW_FLT_KV(json, "x", vertex.x);
JW_FLT_KV(json, "y", vertex.y);
});
});
// Write boolean value
JW_BOOL_KV(json, "filled", shape.filled);
});
// Print the resulting JSON
printf("Modified JSON: %.*s\n", (int)json.length, json.data);
// Cleanup
StrDeinit(&shape.name);
VecDeinit(&shape.vertices);
StrDeinit(&json);
}
#include <Misra.h>
// Complex type with owned resources
typedef struct {
int id;
Vec(int) data;
} ComplexType;
// Copy initialization for deep copying
bool ComplexTypeCopyInit(ComplexType* dst, const ComplexType* src) {
dst->id = src->id;
dst->data = VecInit();
// Copy all elements from source vector
VecForeachIdx(&src->data, val, idx, {
VecInsertR(&dst->data, val, idx);
});
return true;
}
// Proper cleanup of owned resources
void ComplexTypeDeinit(ComplexType* ct) {
VecDeinit(&ct->data);
}
int main() {
// Vector of complex types with resource management
Vec(ComplexType) objects = VecInitWithDeepCopy(ComplexTypeCopyInit, ComplexTypeDeinit);
// Create and insert items
ComplexType item = {
.id = 1,
.data = VecInit()
};
VecInsertR(&item.data, 42, 0);
VecInsertR(&item.data, 43, 1);
// Insert with ownership transfer
VecInsertL(&objects, &item, 0); // item is now owned by vector
// Direct deletion (vector handles cleanup)
// Since we provided ComplexTypeDeinit during initialization,
// the vector will automatically call it when deleting items
VecDelete(&objects, 0); // ComplexTypeDeinit is called automatically
// Cleanup
VecDeinit(&objects); // Calls ComplexTypeDeinit for each remaining element
}
The library supports Rust-style format strings with placeholders in the form {}
or {pecifier}
.
The macro-tricks use _Generic
for compile-time type specific io dispatching. Here's what works and what doesn't:
// String literals (array types like char[6] not handled by _Generic)
StrWriteFmt(&output, "Hello, {}!", "world"); // ERROR: char[6] not in _Generic cases
// Any char array types are not handled
char buffer[20] = "Hello"; // Type: char[20]
StrWriteFmt(&output, "Message: {}", buffer); // ERROR: char[20] not in _Generic cases
const char name[] = "Alice"; // Type: const char[6]
StrWriteFmt(&output, "Name: {}", name); // ERROR: const char[6] not in _Generic cases
// const char* variables (pointer type matches _Generic case)
const char* title = "Mr.";
const char* surname = "Smith";
StrWriteFmt(&output, "{} {}", title, surname); // âś… Works great!
// char* variables (pointer type matches _Generic case)
char* dynamic_str = malloc(50);
strcpy(dynamic_str, "Dynamic");
StrWriteFmt(&output, "Value: {}", dynamic_str); // âś… Works perfectly!
// Str objects (library's string type)
Str greeting = StrInitFromZstr("Welcome");
StrWriteFmt(&output, "Message: {}", greeting);
// Primitive types (all handled by _Generic)
int number = 42;
float pi = 3.14f;
StrWriteFmt(&output, "Number: {}, Pi: {.2}", number, pi);
// For constant strings, use const char* pointers:
const char* program_name = "MyApp"; // âś… const char* works perfectly!
const char* version = "1.0.0"; // âś… char* pointer types work!
// For dynamic strings, use Str objects or char* pointers:
Str user_input = StrInit();
StrReadFmt(input_line, "Name: {}", user_input);
char* allocated = malloc(100);
strcpy(allocated, "Dynamic content");
StrWriteFmt(&message, "Content: {}", allocated); // âś… char* works!
// For function parameters accepting strings:
void log_message(const char* msg) { // âś… const char* parameter
StrWriteFmt(&log_output, "[LOG] {}", msg); // âś… Works perfectly!
}
void process_buffer(char* buffer) { // âś… char* parameter
StrWriteFmt(&output, "Processing: {}", buffer); // âś… Works great!
}
The macro-tricks use _Generic
which only handles these specific types:
const char*
âś…char*
âś…Str
âś…- Primitive types (
int
,float
,u32
, etc.) âś…
But NOT array types like:
char[6]
(from"hello"
) ❌char[20]
(fromchar buffer[20]
) ❌const char[10]
(fromconst char arr[] = "test"
) ❌
The compiler knows these array types perfectly, but _Generic
doesn't have cases for every possible array size.
If no specifier is provided (just {}
), default formatting is used.
Format specifiers can include the following components, which can be combined:
Controls text alignment within a field width:
Specifier | Description |
---|---|
< |
Left-aligned (pad on the right) |
> |
Right-aligned (pad on the left) - default |
^ |
Center-aligned (pad on both sides) |
This is also used to control the endianness of raw data read or written.
Specifies the minimum field width. The value is padded with spaces if it's shorter than this width:
{} // Minimum width of 5 characters, right-aligned
{<5} // Minimum width of 5 characters, left-aligned
{^5} // Minimum width of 5 characters, center-aligned
The endianness specified is used to convert the read data to native endian after reading in specified endianness format.
Specifier | Description |
---|---|
< |
Little Endian (Least significant byte first) |
> |
Big Endian (Most significant byte first, default) |
^ |
Native Endian (Same as host endianness) |
Much like how alignment is specified, width of data read can also be specified in bytes.
{4} // Read/Write 4 bytes in big endian order.
{>4} // Read/Write 4 bytes in big endian order.
{<2} // Read/Write 2 bytes in little endian order.
{^8} // Read/Write 8 bytes in native endian
Specifies the output format for the value:
Specifier | Description | Example Output |
---|---|---|
x |
Hexadecimal format (lowercase) | 0xdeadbeef |
X |
Hexadecimal format (uppercase) | 0xDEADBEEF |
b |
Binary format | 0b10100101 |
o |
Octal format | 0o777 |
c |
Character format (preserve case) | Raw character bytes |
a |
Character format (force lowercase) | Converts characters to lowercase |
A |
Character format (force uppercase) | Converts characters to uppercase |
r |
Raw data reading or writing | \x7fELF (magic bytes of an elf file) |
e |
Scientific notation (lowercase) | 1.235e+02 |
E |
Scientific notation (uppercase) | 1.235E+02 |
For floating-point values, specifies the number of decimal places:
{.2} // Two decimal places
{.0} // No decimal places
{.10} // Ten decimal places
Precision is ignored if specified when reading/writing raw data.
// Correct usage with const char*
const char* greeting = "Hello";
const char* subject = "world";
StrWriteFmt(&output, "{}, {}!", greeting, subject); // "Hello, world!"
// Escaped braces
StrWriteFmt(&output, "{{Hello}}"); // "{Hello}"
const char* str = "Hello"; // const char* variable
// Basic string
StrWriteFmt(&output, "{}", str); // "Hello"
// String with width and alignment
StrWriteFmt(&output, "{>10}", str); // " Hello"
StrWriteFmt(&output, "{<10}", str); // "Hello "
StrWriteFmt(&output, "{^10}", str); // " Hello "
i32 val = 42;
// Default decimal
StrWriteFmt(&output, "{}", val); // "42"
// Hexadecimal
u32 hex_val = 0xDEADBEEF;
StrWriteFmt(&output, "{}", hex_val); // "0xdeadbeef"
StrWriteFmt(&output, "{}", hex_val); // "0xDEADBEEF"
// Binary
u8 bin_val = 0xA5; // 10100101 in binary
StrWriteFmt(&output, "{}", bin_val); // "0b10100101"
// Octal
u16 oct_val = 0777;
StrWriteFmt(&output, "{}", oct_val); // "0o777"
// Width and alignment with numbers
StrWriteFmt(&output, "{}", val); // " 42" (right-aligned)
StrWriteFmt(&output, "{<5}", val); // "42 " (left-aligned)
StrWriteFmt(&output, "{^5}", val); // " 42 " (center-aligned)
The character format specifiers (c
, a
, A
) work with integer types, treating them as character data:
// Single character (u8)
u8 upper_char = 'M';
u8 lower_char = 'm';
StrWriteFmt(&output, "{}", upper_char); // "M" (preserve case)
StrWriteFmt(&output, "{}", upper_char); // "m" (force lowercase)
StrWriteFmt(&output, "{}", lower_char); // "M" (force uppercase)
// Multi-byte integers (interpreted as character sequences)
u16 u16_value = ('A' << 8) | 'B'; // "AB" in big-endian
StrWriteFmt(&output, "{}", u16_value); // "AB" (preserve case)
StrWriteFmt(&output, "{}", u16_value); // "ab" (force lowercase)
StrWriteFmt(&output, "{}", u16_value); // "AB" (force uppercase)
// Works with u32 and u64 as well, treating them as byte sequences
u32 u32_value = ('H' << 24) | ('i' << 16) | ('!' << 8) | '!';
StrWriteFmt(&output, "{}", u32_value); // "Hi!!" (preserve case)
StrWriteFmt(&output, "{}", u32_value); // "hi!!" (force lowercase)
StrWriteFmt(&output, "{}", u32_value); // "HI!!" (force uppercase)
Character format specifiers also work with strings:
const char* mixed_case = "MiXeD CaSe";
StrWriteFmt(&output, "{}", mixed_case); // "MiXeD CaSe" (preserve case)
StrWriteFmt(&output, "{}", mixed_case); // "mixed case" (force lowercase)
StrWriteFmt(&output, "{}", mixed_case); // "MIXED CASE" (force uppercase)
// Also works with Str objects
Str s = StrInitFromZstr("Hello World");
StrWriteFmt(&output, "{}", s); // "hello world"
StrWriteFmt(&output, "{}", s); // "HELLO WORLD"
f64 pi = 3.14159265359;
// Default precision (6 decimal places)
StrWriteFmt(&output, "{}", pi); // "3.141593"
// Custom precision
StrWriteFmt(&output, "{.2}", pi); // "3.14"
StrWriteFmt(&output, "{.0}", pi); // "3"
StrWriteFmt(&output, "{.10}", pi); // "3.1415926536"
// Scientific notation
StrWriteFmt(&output, "{}", 123.456); // "1.235e+02"
StrWriteFmt(&output, "{}", 123.456); // "1.235E+02"
// Custom precision with scientific notation
StrWriteFmt(&output, "{.3e}", 123.456); // "1.235e+02"
// Special values
f64 pos_inf = INFINITY;
f64 neg_inf = -INFINITY;
f64 nan_val = NAN;
StrWriteFmt(&output, "{}", pos_inf); // "inf"
StrWriteFmt(&output, "{}", neg_inf); // "-inf"
StrWriteFmt(&output, "{}", nan_val); // "nan"
The library also supports parsing values from strings using the same format specifier syntax:
// Reading integers
i32 num = 0;
StrReadFmt("42", "{}", num); // num = 42
// Reading hexadecimal (auto-detected with 0x prefix)
u32 hex_val = 0;
StrReadFmt("0xdeadbeef", "{}", hex_val); // hex_val = 0xdeadbeef
// Reading binary (auto-detected with 0b prefix)
i8 bin_val = 0;
StrReadFmt("0b101010", "{}", bin_val); // bin_val = 42
// Reading octal (auto-detected with 0o prefix)
i32 oct_val = 0;
StrReadFmt("0o755", "{}", oct_val); // oct_val = 493
// Reading floating point
f64 value = 0.0;
StrReadFmt("3.14159", "{}", value); // value = 3.14159
// Reading scientific notation
StrReadFmt("1.23e4", "{}", value); // value = 12300.0
// Reading strings
Str name = StrInit();
StrReadFmt("Alice", "{}", name); // name = "Alice"
// Reading quoted strings
StrReadFmt("\"Hello, World!\"", "{}", name); // name = "Hello, World!"
// Reading multiple values
i32 count = 0;
Str user = StrInit();
StrReadFmt("Count: 42, Name: Alice", "Count: {}, Name: {}", count, user);
// count = 42, user = "Alice"
The library provides several I/O functions for formatted reading and writing:
StrWriteFmt(&str, format, ...)
: Append formatted output to a stringStrReadFmt(input, format, ...)
: Parse values from a stringFWriteFmt(file, format, ...)
: Write formatted output to a fileFWriteFmtLn(file, format, ...)
: Write formatted output to a file with a newlineFReadFmt(file, format, ...)
: Read formatted input from a fileWriteFmt(format, ...)
: Write formatted output to stdoutWriteFmtLn(format, ...)
: Write formatted output to stdout with a newlineReadFmt(format, ...)
: Read formatted input from stdin
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is dedicated to the public domain under the Unlicense.
This means you are free to:
- Use the code for any purpose
- Change the code in any way
- Share the code with anyone
- Distribute the code
- Sell the code or derivative works
No attribution is required. See the LICENSE.md file for details.