MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
mmgr_string_shim.h
Go to the documentation of this file.
1/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
3 *
4 * Every use falls under AGPL-3.0-or-later unless you hold explicit permission, which is either a
5 * negotiated commercial licensing contract or an educator's license issued to you personally.
6 */
7/**
8 * @file mmgr_string_shim.h
9 * @brief Redirects the <string.h> names onto MMgr's bounded implementations.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-08-30
12 *
13 * @note For code already written against <string.h> that has to run where there is no libc, or
14 * where an unbounded read is not acceptable. The call sites do not change. What the names
15 * mean does.
16 * @note Defines the usual <string.h> include guards. A later #include <string.h> contributes
17 * nothing and the real declarations never arrive to conflict with these.
18 * @note Not reached from mmgr.h. Including this changes what the <string.h> names mean, which is a
19 * decision a consumer makes for itself.
20 * @warning strcmp, strcasecmp, strncmp and strncasecmp report equality only and never order. Code
21 * that sorts with them is wrong here and compiles anyway.
22 * @warning Including this header changes the meaning of those names for the whole translation unit,
23 * including in headers it includes afterwards.
24 * @warning The six names bounded by MMGR_STR_MAX need the build to define it. Nothing here supplies
25 * a default, because the caller is what states how much storage there is.
26 */
27#ifndef MMGR_STRING_SHIM_H
28#define MMGR_STRING_SHIM_H
29
30#include "mmgr.h"
31
34
35/**
36 * @brief Claims the include guards several C libraries use for <string.h>.
37 *
38 * @note A later #include <string.h> then expands to nothing, leaving these macros in place.
39 * @warning A library whose guard is not among these five will still declare the real functions.
40 */
41#ifndef _STRING_H
42#define _STRING_H 1
43#endif
44#ifndef _STRING_H_
45#define _STRING_H_ 1
46#endif
47#ifndef __STRING_H__
48#define __STRING_H__ 1
49#endif
50#ifndef _STRING_H_INCLUDED
51#define _STRING_H_INCLUDED 1
52#endif
53#ifndef _INC_STRING
54#define _INC_STRING 1
55#endif
56
57EMBED_BEGIN_DECLS
58
59/**
60 * @brief Copies bytes from source to dest and returns dest.
61 *
62 * @param[out] dest Destination to write [BORROWS].
63 * @param[in] source Bytes to read [BORROWS].
64 * @param[in] bytes Number of bytes to copy.
65 * @return dest [BORROWS].
66 * @warning The two regions must not overlap. Use mmgr_shim_move when they might.
67 * @warning dest must be writable for bytes and source readable for the same.
68 */
69EMBED_INLINE void *mmgr_shim_cpy(void *dest, const void *source, size_t bytes)
70{
71 EMBED_CALL(memor.cpy, MemoriaCfg, .dst = dest, .src = source, .bytes = bytes);
72 return dest;
73}
74
75/**
76 * @brief Copies bytes from source to dest, tolerating overlap, and returns dest.
77 *
78 * @param[out] dest Destination to write [BORROWS].
79 * @param[in] source Bytes to read [BORROWS].
80 * @param[in] bytes Number of bytes to copy.
81 * @return dest [BORROWS].
82 * @note Compares the two addresses and walks upwards or downwards so overlapping bytes are read
83 * before they are written.
84 * @warning dest must be writable for bytes and source readable for the same.
85 */
86EMBED_INLINE void *mmgr_shim_move(void *dest, const void *source, size_t bytes)
87{
88 // Explicit casts to a byte address, because a relational test wants both sides pointing at a
89 // complete object type and void is not one. The comparison picks which way the copy walks
90 if ((const uint8_t *)dest <= (const uint8_t *)source)
91 {
92 EMBED_CALL(memor.move_down, MemoriaCfg, .dst = dest, .src = source, .bytes = bytes);
93 }
94 else
95 {
96 EMBED_CALL(memor.move_up, MemoriaCfg, .dst = dest, .src = source, .bytes = bytes);
97 }
98 return dest;
99}
100
101/**
102 * @brief Fills bytes of dest with the low byte of value and returns dest.
103 *
104 * @param[out] dest Destination to write [BORROWS].
105 * @param[in] value Fill value, of which only the low eight bits are used.
106 * @param[in] bytes Number of bytes to write.
107 * @return dest [BORROWS].
108 * @note Explicit cast narrows value to uint8_t, matching the byte the fill writes.
109 * @warning dest must be writable for bytes.
110 */
111EMBED_INLINE void *mmgr_shim_set(void *dest, embed_iword value, size_t bytes)
112{
113 EMBED_CALL(memor.set, MemoriaCfg, .dst = dest, .val = (uint8_t)value, .bytes = bytes);
114 return dest;
115}
116
117/**
118 * @brief Compares bytes of left against right.
119 *
120 * @param[in] left First region [BORROWS].
121 * @param[in] right Second region [BORROWS].
122 * @param[in] bytes Number of bytes to compare.
123 * @return Difference of the first unequal byte pair, or 0 when all bytes match.
124 * @note Unlike the strcmp shims, this one does order: the sign follows the differing bytes.
125 * @warning Both left and right must be readable for bytes.
126 */
127EMBED_INLINE embed_iword mmgr_shim_cmp(const void *left, const void *right, size_t bytes)
128{
129 return EMBED_CALL(memor.cmp, MemoriaCfg, .src = left, .other = right, .bytes = bytes);
130}
131
132/**
133 * @brief Finds the first byte in region equal to the low byte of value.
134 *
135 * @param[in] region Bytes to search [BORROWS].
136 * @param[in] value Byte sought, of which only the low eight bits are used.
137 * @param[in] bytes Number of bytes to search.
138 * @return Address of the match, or NULL when the byte does not occur [BORROWS].
139 * @note The cast through size_t drops the const the backend returns, matching the memchr signature.
140 * @warning region must be readable for bytes.
141 */
142EMBED_INLINE void *mmgr_shim_chr(const void *region, embed_iword value, size_t bytes)
143{
144 return (void *)(size_t)EMBED_CALL(memor.chr, MemoriaCfg, .src = region, .bytes = bytes, .val = (uint8_t)value);
145}
146
147/**
148 * @brief Replaces memcpy with mmgr_shim_cpy.
149 *
150 * @note Every argument is parenthesized. Any expression may be passed.
151 */
152#define memcpy(dest, source, bytes) mmgr_shim_cpy((dest), (source), (bytes))
153
154/**
155 * @brief Replaces memmove with mmgr_shim_move.
156 *
157 * @note Every argument is parenthesized, as in the memcpy shim.
158 */
159#define memmove(dest, source, bytes) mmgr_shim_move((dest), (source), (bytes))
160
161/**
162 * @brief Replaces memset with mmgr_shim_set.
163 *
164 * @note value reaches mmgr_shim_set as embed_iword and is narrowed there, the signature the
165 * real memset has and the reason the shim takes a word and not a byte.
166 */
167#define memset(dest, value, bytes) mmgr_shim_set((dest), (value), (bytes))
168
169/**
170 * @brief Replaces memcmp with mmgr_shim_cmp.
171 *
172 * @note Keeps the ordering the standard requires. The sign follows the first differing byte pair.
173 */
174#define memcmp(left, right, bytes) mmgr_shim_cmp((left), (right), (bytes))
175
176/**
177 * @brief Replaces memchr with mmgr_shim_chr.
178 *
179 * @note The only one of the five mem shims that is bounded by its own argument and not by a
180 * terminator, and so it needs no MMGR_STR_MAX and cannot stop short of what it was asked for.
181 */
182#define memchr(region, value, bytes) mmgr_shim_chr((region), (value), (bytes))
183
184/**
185 * @brief Replaces strlen with a scan bounded at MMGR_STR_MAX.
186 *
187 * @return Bytes before the terminator, at most MMGR_STR_MAX.
188 * @warning Returns MMGR_STR_MAX when no terminator appears within it, instead of scanning further.
189 * @warning text must be readable until its terminator, or for MMGR_STR_MAX bytes when it has none,
190 * with the tail word read whole as mmgr_cellul_len describes.
191 */
192#define strlen(text) EMBED_CALL(cellul.len, CatenaFinitaCfg, .src = (text), .cap = MMGR_STR_MAX)
193
194/**
195 * @brief Replaces strnlen with a scan bounded at limit.
196 *
197 * @return Bytes before the terminator, at most limit.
198 * @note Returns limit when no terminator appears within it, as strnlen does.
199 * @warning text must be readable until its terminator, or for limit bytes when it has none, with the
200 * tail word read whole as mmgr_cellul_len describes.
201 */
202#define strnlen(text, limit) EMBED_CALL(cellul.len, CatenaFinitaCfg, .src = (text), .cap = (limit))
203
204/**
205 * @brief Replaces strstr with a case-sensitive search bounded at MMGR_STR_MAX.
206 *
207 * @return Address inside haystack where needle begins, or NULL when it does not occur [BORROWS].
208 * @note The cast through size_t drops the const the backend returns, matching the strstr signature.
209 * @warning Both operands must be terminated within MMGR_STR_MAX. Without a terminator the search
210 * reads that many bytes from them.
211 */
212#define strstr(haystack, needle) \
213 ((char *)(size_t)EMBED_CALL(cellul.find, CatenaFinitaCfg, .src = (haystack), .cap = MMGR_STR_MAX, \
214 .other = (needle), .other_cap = MMGR_STR_MAX, .ci = EMBED_FALSE))
215
216/**
217 * @brief Replaces strcasestr with a case-folded search bounded at MMGR_STR_MAX.
218 *
219 * @return Address inside haystack where needle begins, or NULL when it does not occur [BORROWS].
220 * @note Differs from the strstr shim only in passing ci as EMBED_TRUE.
221 * @warning Both operands must be terminated within MMGR_STR_MAX. Without a terminator the search
222 * reads that many bytes from them.
223 */
224#define strcasestr(haystack, needle) \
225 ((char *)(size_t)EMBED_CALL(cellul.find, CatenaFinitaCfg, .src = (haystack), .cap = MMGR_STR_MAX, \
226 .other = (needle), .other_cap = MMGR_STR_MAX, .ci = EMBED_TRUE))
227
228/**
229 * @brief Replaces strcmp with an equality test bounded at MMGR_STR_MAX.
230 *
231 * @return 0 when left and right hold the same string, 1 when they differ.
232 * @note Both == 0 and ! therefore test equality, as they do with the real strcmp.
233 * @warning Never negative and never above 1: it cannot be used to order strings.
234 * @warning Both operands must be terminated within MMGR_STR_MAX. Without a terminator the compare
235 * reads that many bytes from them.
236 */
237#define strcmp(left, right) \
238 (!EMBED_CALL(cellul.eq, CatenaFinitaCfg, .src = (left), .other = (right), .cap = MMGR_STR_MAX, .ci = EMBED_FALSE))
239
240/**
241 * @brief Replaces strcasecmp with a case-folded equality test bounded at MMGR_STR_MAX.
242 *
243 * @return 0 when left and right hold the same string ignoring case, 1 when they differ.
244 * @warning Never negative and never above 1: it cannot be used to order strings.
245 * @warning Both operands must be terminated within MMGR_STR_MAX. Without a terminator the compare
246 * reads that many bytes from them.
247 */
248#define strcasecmp(left, right) \
249 (!EMBED_CALL(cellul.eq, CatenaFinitaCfg, .src = (left), .other = (right), .cap = MMGR_STR_MAX, .ci = EMBED_TRUE))
250
251/**
252 * @brief Replaces strncmp with an equality test over at most limit bytes.
253 *
254 * @return 0 when left and right agree through limit bytes, 1 when they differ before it.
255 * @warning Never negative and never above 1: it cannot be used to order strings.
256 * @warning A terminator does not end the comparison. All limit bytes must be readable in both
257 * operands, with the tail word read whole as mmgr_cellul_diff describes.
258 * @warning limit appears twice in the expansion. An argument with a side effect is evaluated twice.
259 */
260#define strncmp(left, right, limit) \
261 (EMBED_CALL(cellul.diff, CatenaFinitaCfg, .src = (left), .other = (right), .cap = (limit), .ci = EMBED_FALSE) < \
262 (limit))
263
264/**
265 * @brief Replaces strncasecmp with a case-folded equality test over at most limit bytes.
266 *
267 * @return 0 when left and right agree through limit bytes ignoring case, 1 when they differ before it.
268 * @warning Never negative and never above 1: it cannot be used to order strings.
269 * @warning A terminator does not end the comparison. All limit bytes must be readable in both
270 * operands, with the tail word read whole as mmgr_cellul_diff describes.
271 * @warning limit appears twice in the expansion. An argument with a side effect is evaluated twice.
272 */
273#define strncasecmp(left, right, limit) \
274 (EMBED_CALL(cellul.diff, CatenaFinitaCfg, .src = (left), .other = (right), .cap = (limit), .ci = EMBED_TRUE) < \
275 (limit))
276
277/**
278 * @brief Replaces strlcpy with a bounded copy that always terminates unless limit is 0.
279 *
280 * @return Bytes copied and not counting the terminator.
281 * @warning Not the source length the real strlcpy returns. A caller cannot detect truncation by
282 * comparing the result against limit.
283 * @warning dest must be writable for limit bytes and source readable for limit minus one.
284 */
285#define strlcpy(dest, source, limit) \
286 EMBED_CALL(cellul.copy, CatenaFinitaCfg, .dst = (dest), .src = (source), .cap = (limit))
287
288/**
289 * @brief Replaces strchr with a search bounded at MMGR_STR_MAX.
290 *
291 * @return Address inside text where the byte occurs, or NULL when it does not [BORROWS].
292 * @note A value of 0 returns the address of the terminator, as strchr does.
293 * @note The cast through size_t drops the const the backend returns, matching the strchr signature,
294 * and value is narrowed to uint8_t, the byte the search compares against.
295 * @warning text must be terminated within MMGR_STR_MAX. Without a terminator the search reads that
296 * many bytes from it.
297 */
298#define strchr(text, value) \
299 ((char *)(size_t)EMBED_CALL(cellul.chr, CatenaFinitaCfg, .src = (text), .cap = MMGR_STR_MAX, \
300 .byte = (uint8_t)(value)))
301
302EMBED_END_DECLS
303
304#endif
Bounded string work: the three argument types, the calls, and the cellul dispatch table.
static void * mmgr_shim_set(void *dest, embed_iword value, size_t bytes)
Fills bytes of dest with the low byte of value and returns dest.
static void * mmgr_shim_move(void *dest, const void *source, size_t bytes)
Copies bytes from source to dest, tolerating overlap, and returns dest.
static void * mmgr_shim_chr(const void *region, embed_iword value, size_t bytes)
Finds the first byte in region equal to the low byte of value.
static embed_iword mmgr_shim_cmp(const void *left, const void *right, size_t bytes)
Compares bytes of left against right.
static void * mmgr_shim_cpy(void *dest, const void *source, size_t bytes)
Copies bytes from source to dest and returns dest.
Byte-level memory work: its arguments, the calls, and the memor dispatch table.
The embedded_types headers every module builds on.
Arguments for the memor calls.
void *const dst
const void *const src