authorgravatar for chrboesch@noreply.codeberg.orgChris Boesch <chrboesch@noreply.codeberg.org> 2026-04-22 01:57:58+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-22 01:57:58+02:00
log3ea77badf94d996740337f6ce6e6612178627dd1
tree5e177ffd5eeeff45537e1258f0c4b5f59e4236c1
parent22945fbbdc1ec89def58f39ddc8c35502b6e05dd

libc: strdup, strndup, wcsdup (#31935)

Contributes to: #30978 Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31935 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

11 files changed, 108 insertions(+), 38 deletions(-)

lib/c/string.zig+20
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const symbol = @import("../c.zig").symbol;3const symbol = @import("../c.zig").symbol;
4const c = std.c;
45
5comptime {6comptime {
6 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {7 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
...@@ -24,6 +25,8 @@ comptime {...@@ -24,6 +25,8 @@ comptime {
24 symbol(&strpbrk, "strpbrk");25 symbol(&strpbrk, "strpbrk");
25 symbol(&strstr, "strstr");26 symbol(&strstr, "strstr");
26 symbol(&strtok, "strtok");27 symbol(&strtok, "strtok");
28 symbol(&strdup, "strdup");
29 symbol(&strndup, "strndup");
27 // strlen is in compiler_rt30 // strlen is in compiler_rt
2831
29 symbol(&strtok_r, "strtok_r");32 symbol(&strtok_r, "strtok_r");
...@@ -164,6 +167,23 @@ fn strtok(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char) ca...@@ -164,6 +167,23 @@ fn strtok(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char) ca
164 return strtok_r(maybe_str, values, &state.str);167 return strtok_r(maybe_str, values, &state.str);
165}168}
166169
170fn strdup(str: [*:0]const c_char) callconv(.c) ?[*:0]c_char {
171 const len = std.mem.len(str);
172 const d_opaque = c.malloc(len + 1) orelse return null;
173 const d: [*]c_char = @ptrCast(d_opaque);
174 @memcpy(d[0 .. len + 1], str[0 .. len + 1]);
175 return @ptrCast(d);
176}
177
178fn strndup(str: [*:0]const c_char, n: usize) callconv(.c) ?[*:0]c_char {
179 const len = strnlen(str, n);
180 const d_opaque = c.malloc(len + 1) orelse return null;
181 const d: [*]c_char = @ptrCast(d_opaque);
182 @memcpy(d[0..len], str[0..len]);
183 d[len] = 0;
184 return @ptrCast(d);
185}
186
167// strlen is in compiler_rt187// strlen is in compiler_rt
168188
169fn strtok_r(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char, noalias state: *?[*:0]c_char) callconv(.c) ?[*:0]c_char {189fn strtok_r(noalias maybe_str: ?[*:0]c_char, noalias values: [*:0]const c_char, noalias state: *?[*:0]c_char) callconv(.c) ?[*:0]c_char {
lib/c/wchar.zig+10
...@@ -5,6 +5,7 @@ const wint_t = std.c.wint_t;...@@ -5,6 +5,7 @@ const wint_t = std.c.wint_t;
5const wchar_t = std.c.wchar_t;5const wchar_t = std.c.wchar_t;
66
7const symbol = @import("../c.zig").symbol;7const symbol = @import("../c.zig").symbol;
8const c = std.c;
89
9comptime {10comptime {
10 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {11 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
...@@ -30,6 +31,7 @@ comptime {...@@ -30,6 +31,7 @@ comptime {
30 symbol(&wcspbrk, "wcspbrk");31 symbol(&wcspbrk, "wcspbrk");
31 symbol(&wcstok, "wcstok");32 symbol(&wcstok, "wcstok");
32 symbol(&wcsstr, "wcsstr");33 symbol(&wcsstr, "wcsstr");
34 symbol(&wcsdup, "wcsdup");
33 symbol(&wcswcs, "wcswcs");35 symbol(&wcswcs, "wcswcs");
34 }36 }
3537
...@@ -189,3 +191,11 @@ fn wcsstr(noalias haystack: [*:0]const wchar_t, noalias needle: [*:0]const wchar...@@ -189,3 +191,11 @@ fn wcsstr(noalias haystack: [*:0]const wchar_t, noalias needle: [*:0]const wchar
189fn wcswcs(noalias haystack: [*:0]const wchar_t, noalias needle: [*:0]const wchar_t) callconv(.c) ?[*:0]wchar_t {191fn wcswcs(noalias haystack: [*:0]const wchar_t, noalias needle: [*:0]const wchar_t) callconv(.c) ?[*:0]wchar_t {
190 return wcsstr(haystack, needle);192 return wcsstr(haystack, needle);
191}193}
194
195fn wcsdup(str: [*:0]const wchar_t) callconv(.c) ?[*:0]wchar_t {
196 const len = wcslen(str);
197 const size = (len + 1) * @sizeOf(wchar_t);
198 const d_opaque = c.malloc(size) orelse return null;
199 const d: [*]wchar_t = @ptrCast(@alignCast(d_opaque));
200 return @ptrCast(wmemcpy(d, str, len + 1));
201}
lib/libc/musl/src/string/strdup.c deleted-10
...@@ -1,10 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3
4char *strdup(const char *s)
5{
6 size_t l = strlen(s);
7 char *d = malloc(l+1);
8 if (!d) return NULL;
9 return memcpy(d, s, l+1);
10}
lib/libc/musl/src/string/strndup.c deleted-12
...@@ -1,12 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3
4char *strndup(const char *s, size_t n)
5{
6 size_t l = strnlen(s, n);
7 char *d = malloc(l+1);
8 if (!d) return NULL;
9 memcpy(d, s, l);
10 d[l] = 0;
11 return d;
12}
lib/libc/musl/src/string/wcsdup.c deleted-10
...@@ -1,10 +0,0 @@
1#include <stdlib.h>
2#include <wchar.h>
3
4wchar_t *wcsdup(const wchar_t *s)
5{
6 size_t l = wcslen(s);
7 wchar_t *d = malloc((l+1)*sizeof(wchar_t));
8 if (!d) return NULL;
9 return wmemcpy(d, s, l+1);
10}
lib/std/c.zig+3
...@@ -11136,6 +11136,9 @@ pub extern "c" fn swab(noalias from: *const anyopaque, noalias to: *anyopaque, n...@@ -11136,6 +11136,9 @@ pub extern "c" fn swab(noalias from: *const anyopaque, noalias to: *anyopaque, n
11136pub extern "c" fn strncmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) c_int;11136pub extern "c" fn strncmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) c_int;
11137pub extern "c" fn strcasecmp(a: [*:0]const c_char, b: [*:0]const c_char) c_int;11137pub extern "c" fn strcasecmp(a: [*:0]const c_char, b: [*:0]const c_char) c_int;
11138pub extern "c" fn strncasecmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) c_int;11138pub extern "c" fn strncasecmp(a: [*:0]const c_char, b: [*:0]const c_char, max: usize) c_int;
11139pub extern "c" fn strdup(s: [*:0]const c_char) ?[*:0]c_char;
11140pub extern "c" fn strndup(s: [*:0]const c_char, n: usize) ?[*:0]c_char;
11141pub extern "c" fn wcsdup(s: [*:0]const wchar_t) ?[*:0]wchar_t;
1113911142
11140pub extern "c" fn ffs(i: c_int) c_int;11143pub extern "c" fn ffs(i: c_int) c_int;
11141pub extern "c" fn ffsl(i: c_long) c_long;11144pub extern "c" fn ffsl(i: c_long) c_long;
src/libs/musl.zig-3
...@@ -1494,14 +1494,11 @@ const src_files = [_][]const u8{...@@ -1494,14 +1494,11 @@ const src_files = [_][]const u8{
1494 "musl/src/stdlib/strtod.c",1494 "musl/src/stdlib/strtod.c",
1495 "musl/src/stdlib/wcstod.c",1495 "musl/src/stdlib/wcstod.c",
1496 "musl/src/stdlib/wcstol.c",1496 "musl/src/stdlib/wcstol.c",
1497 "musl/src/string/strdup.c",
1498 "musl/src/string/strerror_r.c",1497 "musl/src/string/strerror_r.c",
1499 "musl/src/string/strndup.c",
1500 "musl/src/string/strsignal.c",1498 "musl/src/string/strsignal.c",
1501 "musl/src/string/strverscmp.c",1499 "musl/src/string/strverscmp.c",
1502 "musl/src/string/wcscasecmp.c",1500 "musl/src/string/wcscasecmp.c",
1503 "musl/src/string/wcscasecmp_l.c",1501 "musl/src/string/wcscasecmp_l.c",
1504 "musl/src/string/wcsdup.c",
1505 "musl/src/string/wcsncasecmp.c",1502 "musl/src/string/wcsncasecmp.c",
1506 "musl/src/string/wcsncasecmp_l.c",1503 "musl/src/string/wcsncasecmp_l.c",
1507 "musl/src/temp/mkdtemp.c",1504 "musl/src/temp/mkdtemp.c",
src/libs/wasi_libc.zig-3
...@@ -902,13 +902,10 @@ const libc_top_half_src_files = [_][]const u8{...@@ -902,13 +902,10 @@ const libc_top_half_src_files = [_][]const u8{
902 "musl/src/stdlib/ecvt.c",902 "musl/src/stdlib/ecvt.c",
903 "musl/src/stdlib/fcvt.c",903 "musl/src/stdlib/fcvt.c",
904 "musl/src/stdlib/gcvt.c",904 "musl/src/stdlib/gcvt.c",
905 "musl/src/string/strdup.c",
906 "musl/src/string/strerror_r.c",905 "musl/src/string/strerror_r.c",
907 "musl/src/string/strndup.c",
908 "musl/src/string/strverscmp.c",906 "musl/src/string/strverscmp.c",
909 "musl/src/string/wcscasecmp.c",907 "musl/src/string/wcscasecmp.c",
910 "musl/src/string/wcscasecmp_l.c",908 "musl/src/string/wcscasecmp_l.c",
911 "musl/src/string/wcsdup.c",
912 "musl/src/string/wcsncasecmp.c",909 "musl/src/string/wcsncasecmp.c",
913 "musl/src/string/wcsncasecmp_l.c",910 "musl/src/string/wcsncasecmp_l.c",
914 "musl/src/thread/default_attr.c",911 "musl/src/thread/default_attr.c",
test/c.zig+1
...@@ -10,4 +10,5 @@ test {...@@ -10,4 +10,5 @@ test {
10 _ = @import("c/string.zig");10 _ = @import("c/string.zig");
11 _ = @import("c/strings.zig");11 _ = @import("c/strings.zig");
12 _ = @import("c/unistd.zig");12 _ = @import("c/unistd.zig");
13 _ = @import("c/wchar.zig");
13}14}
test/c/string.zig+40
...@@ -10,3 +10,43 @@ test "strncmp" {...@@ -10,3 +10,43 @@ test "strncmp" {
10 try testing.expect(c.strncmp(@ptrCast("b"), @ptrCast("a"), 1) > 0);10 try testing.expect(c.strncmp(@ptrCast("b"), @ptrCast("a"), 1) > 0);
11 try testing.expect(c.strncmp(@ptrCast("\xff"), @ptrCast("\x02"), 1) > 0);11 try testing.expect(c.strncmp(@ptrCast("\xff"), @ptrCast("\x02"), 1) > 0);
12}12}
13
14test "strdup" {
15 const org: [*:0]const u8 = "a";
16 const cpy_opt = c.strdup(@ptrCast(org));
17 const cpy = cpy_opt orelse return error.OutOfMemory;
18 defer c.free(cpy);
19
20 const cpy_u8: [*:0]u8 = @ptrCast(cpy);
21 try testing.expectEqualStrings(std.mem.span(org), std.mem.span(@as([*:0]const u8, cpy_u8)));
22 try testing.expect(@intFromPtr(cpy_u8) != @intFromPtr(org));
23
24 cpy_u8[0] = 'b';
25 try testing.expectEqualStrings("a", std.mem.span(org));
26 try testing.expectEqualStrings("b", std.mem.span(@as([*:0]const u8, cpy_u8)));
27}
28
29test "strndup" {
30 if (builtin.target.os.tag == .windows) return; // no strndup
31 const org1: [*:0]const u8 = "Hello";
32
33 const copy1_opt = c.strndup(@ptrCast(org1), 100);
34 const copy1 = copy1_opt orelse return error.OutOfMemory;
35 defer c.free(copy1);
36 const copy1_u8: [*:0]u8 = @ptrCast(copy1);
37 try testing.expectEqualStrings("Hello", std.mem.span(@as([*:0]const u8, copy1_u8)));
38
39 const org2: [*:0]const u8 = "Hello World!";
40 const copy2_opt = c.strndup(@ptrCast(org2), 5);
41 const copy2 = copy2_opt orelse return error.OutOfMemory;
42 defer c.free(copy2);
43 const copy2_u8: [*:0]u8 = @ptrCast(copy2);
44 try testing.expectEqualStrings("Hello", std.mem.span(@as([*:0]const u8, copy2_u8)));
45 try testing.expectEqual(@as(usize, 5), std.mem.len(copy2_u8));
46
47 const copy3_opt = c.strndup(@ptrCast(org1), 5);
48 const copy3 = copy3_opt orelse return error.OutOfMemory;
49 defer c.free(copy3);
50 const copy3_u8: [*:0]u8 = @ptrCast(copy3);
51 try testing.expectEqualStrings("Hello", std.mem.span(@as([*:0]const u8, copy3_u8)));
52}
test/c/wchar.zig created+34
...@@ -0,0 +1,34 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const c = std.c;
5const testing = std.testing;
6
7test "wcsdup" {
8 const org: [*:0]const c.wchar_t = &[_:0]c.wchar_t{ 'H', 'e', 'l', 'l', 'o' };
9 const cpy_opt = c.wcsdup(org);
10 const cpy = cpy_opt orelse return error.OutOfMemory;
11 defer c.free(cpy);
12
13 try testing.expectEqual(@as(usize, 5), std.mem.len(@as([*:0]const c.wchar_t, cpy)));
14
15 try testing.expectEqual(@as(c.wchar_t, 'H'), cpy[0]);
16 try testing.expectEqual(@as(c.wchar_t, 'e'), cpy[1]);
17 try testing.expectEqual(@as(c.wchar_t, 'l'), cpy[2]);
18 try testing.expectEqual(@as(c.wchar_t, 'l'), cpy[3]);
19 try testing.expectEqual(@as(c.wchar_t, 'o'), cpy[4]);
20 try testing.expectEqual(@as(c.wchar_t, 0), cpy[5]);
21
22 try testing.expect(@intFromPtr(cpy) != @intFromPtr(org));
23
24 cpy[0] = 'B';
25 try testing.expectEqual(@as(c.wchar_t, 'H'), org[0]);
26 try testing.expectEqual(@as(c.wchar_t, 'B'), cpy[0]);
27}
28
29test "wcsdup empty string" {
30 const org: [*:0]const c.wchar_t = &[_:0]c.wchar_t{};
31 const cpy = c.wcsdup(org) orelse return error.OutOfMemory;
32 defer c.free(cpy);
33 try testing.expectEqual(@as(c.wchar_t, 0), cpy[0]);
34}