Thanks for your answers, they’ve been really useful for me to understand how the type system, the different types of pointers and the sentinels all go together.
However, what I meant was, if I have a C function that takes an array of strings (const char *const *
) and a zig function that returns a dynamically-allocated list of strings ([][]const u8
), how can I pass the former to the latter?
I have built this example in which I allocate a new copy of the string in a way that can be understood by the C function, but I wonder if there’s a better way to do it:
This is my C code:
#include <stdio.h>
void printStringList(const char *const *list) {
for (int i = 0; list[i]; i++) {
for (int j = 0; list[i][j]; j++) {
printf("%c", list[i][j]);
}
printf("\n");
}
}
And I have this zig function that returns a slice of strings:
fn generateStrings(allocator: std.mem.Allocator) ![][]const u8 {
const n_strings = 2;
const string_len = 4;
var str_list: [][]const u8 = try allocator.alloc([]const u8, n_strings);
for (0..n_strings) |i| {
const str: []u8 = try allocator.alloc(u8, string_len);
for (0..string_len) |j| {
str[j] = 'a';
}
str_list[i] = str;
}
return str_list;
}
What I want to do if feed the output of generateStrings()
into printStringList()
, but the problem is that printStringList()
expects a null terminator in each string and in the array containing the strings. Because the array and the strings are dynamically allocated, this requires allocating new memory.
Also (I may be wrong on this, please correct me if that’s the case), since I’m working with slices, I can’t guarantee that I can increase the size of the array and/or the strings, since the new memory needs to be adjacent to the one previously allocated.
The solution I came up with was creating a new null-terminated array, and adding a null-terminated copy of each string:
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const dynamic_string_list = try generateStrings(allocator);
defer {
// Do I need to free each string individually?
for (dynamic_string_list) |str| {
allocator.free(str);
}
allocator.free(dynamic_string_list);
}
const c_compatible_string_list = try allocator.allocSentinel(
?[*:0]u8,
dynamic_string_list.len,
null,
);
defer allocator.free(c_compatible_string_list);
for (0..c_compatible_string_list.len) |i| {
c_compatible_string_list[i] = try allocator.dupeZ(u8, dynamic_string_list[i]);
}
defer {
for (c_compatible_string_list, dynamic_string_list) |c_str, d_str| {
allocator.free(c_str.?[0 .. d_str.len + 1]);
}
allocator.free(c_compatible_string_list);
}
// I can call the C function like this:
std.debug.print("Dynamic strings:\n", .{});
c.printStringList(@ptrCast(c_compatible_string_list));
Is there a better way to do this?