Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions L2_Core/utils/inc/ek_str.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef EK_STR_H
#define EK_STR_H

#include "../../../ek_conf.h"

#if EK_STR_ENABLE == 1

# include "ek_def.h"

typedef struct ek_str_t ek_str_t;

struct ek_str_t
{
char *buf;
uint32_t cap;
uint32_t len;
};

ek_str_t *ek_str_create(const char *str);
void ek_str_free(ek_str_t *s);
void ek_str_clear(ek_str_t *s);

bool ek_str_append_len(ek_str_t *s, const char *str, uint32_t len);
bool ek_str_append(ek_str_t *s, const char *str);
bool ek_str_append_fmt(ek_str_t *s, const char *fmt, ...);

bool ek_str_cat(ek_str_t *dst, ek_str_t *src);
ek_str_t *ek_str_slice(const ek_str_t *s, int32_t start, int32_t end);
const char *ek_str_get_cstring(ek_str_t *s);

uint32_t ek_str_get_len(ek_str_t *s);
uint32_t ek_str_get_cap(ek_str_t *s);

int ek_str_cmp(ek_str_t *s1, ek_str_t *s2);
int ek_str_ncmp(ek_str_t *s1, ek_str_t *s2, size_t n);

Comment on lines +19 to +36

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str.h 声明了对外函数接口,但没有像其它 utils API 头文件那样加 #ifdef __cplusplus / extern "C" 包裹,C++ 工程中包含该头会发生符号名改编导致链接失败。建议参照 L2_Core/utils/inc/ek_mem.h:15-18ek_stack.h:42-45 的写法补上。

Copilot uses AI. Check for mistakes.
#endif // EK_STR_ENABLE

#endif // EK_STR_H
211 changes: 211 additions & 0 deletions L2_Core/utils/src/ek_str.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#include "../inc/ek_str.h"

#if EK_STR_ENABLE == 1

# include "../inc/ek_io.h"
# include "../inc/ek_mem.h"
# include "../inc/ek_assert.h"

# define INDEX_CLAMP(idx, len) \
do \
{ \
if ((idx) < 0) (idx) += (len); \
if ((idx) < 0) (idx) = 0; \
if ((idx) > (len)) (idx) = (len); \
} while (0)

static bool _ek_str_ensure_cap(ek_str_t *s, uint32_t len)
{
ek_assert_tiny(s != NULL);

if (s->cap >= len) return true;

uint32_t new_cap = s->cap;
do
{
if (new_cap == 0) new_cap = 2;
new_cap += new_cap / 2;
} while (new_cap < len);

char *buf = NULL;

if (s->buf == NULL) buf = ek_malloc(len);

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ek_str_ensure_caps->buf == NULL 分支用 ek_malloc(len) 分配,但随后把 s->cap 设为 new_cap。当 new_cap > len 时会导致 cap 与实际分配大小不一致,后续写入可能越界。建议这里按 new_cap 分配(并保持 cap 与实际 buffer 大小一致)。

Suggested change
if (s->buf == NULL) buf = ek_malloc(len);
if (s->buf == NULL) buf = ek_malloc(new_cap);

Copilot uses AI. Check for mistakes.
else buf = ek_realloc(s->buf, new_cap);

if (buf == NULL) return false;

s->buf = buf;
s->cap = new_cap;

return true;
}

ek_str_t *ek_str_create(const char *str)
{
ek_str_t *s = ek_malloc(sizeof(ek_str_t));
if (s == NULL) return NULL;

s->buf = NULL;
s->cap = 0;
s->len = 0;

// 传入 NULL 则创建一个空的字符串
if (str == NULL) return s;

if (ek_str_append(s, str) == false)
{
ek_free(s);
return NULL;
}

return s;
}

void ek_str_free(ek_str_t *s)
{
ek_assert_tiny(s != NULL);

s->cap = 0;
s->len = 0;
ek_free(s->buf);
ek_free(s);
}

void ek_str_clear(ek_str_t *s)
{
ek_assert_tiny(s != NULL);

s->len = 0;

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_clear 只把 len 置 0,但如果 buf 已分配,ek_str_get_cstring 仍会返回旧内容(因为未写入 buf[0] = '\0')。建议 clear 时同步写入终止符,确保 C 字符串语义正确。

Suggested change
s->len = 0;
s->len = 0;
if (s->buf != NULL)
{
s->buf[0] = '\0';
}

Copilot uses AI. Check for mistakes.
}

bool ek_str_append_len(ek_str_t *s, const char *str, uint32_t len)
{
ek_assert_tiny(s != NULL);

// 如果传入追加内容为空,并且字符长度也为空
// 什么也不做
if (str == NULL && len == 0) return true;

ek_assert_tiny(str != NULL);
ek_assert_tiny(len != 0);
Comment on lines +85 to +90

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_append_lenlen == 0 做了断言(即使 str != NULL),会导致 ek_str_append(s, "") / ek_str_create("") 直接触发断言;同时 ek_str_slicestart == end 时调用 ek_str_create("") 也会因此崩溃。建议把 len == 0(无论 str 是否为 NULL)当作 no-op 并直接返回 true。

Suggested change
// 如果传入追加内容为空,并且字符长度也为空
// 什么也不做
if (str == NULL && len == 0) return true;
ek_assert_tiny(str != NULL);
ek_assert_tiny(len != 0);
// 长度为 0 时,无论 str 是否为 NULL,都视为 no-op
if (len == 0) return true;
// 只有在真正需要读取数据时才要求 str 非空
ek_assert_tiny(str != NULL);

Copilot uses AI. Check for mistakes.

if (_ek_str_ensure_cap(s, s->len + len + 1) == false) return false;
memcpy(s->buf + s->len, str, len);

s->len += len;
s->buf[s->len] = '\0';

return true;
}

bool ek_str_append_fmt(ek_str_t *s, const char *fmt, ...)
{
ek_assert_tiny(s != NULL);

// 获取格式化字符串的长度
va_list args;
va_start(args, fmt);
int len = lwvsnprintf(NULL, 0, fmt, args);
va_end(args);

if (len < 0) return false;

if (_ek_str_ensure_cap(s, s->len + len) == false) return false;

va_start(args, fmt);
// +1 给 \0
lwvsnprintf(s->buf + s->len, len + 1, fmt, args);
va_end(args);
Comment on lines +111 to +118

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_append_fmtlwvsnprintf 会写入 len + 1(包含 \0),但 _ek_str_ensure_cap 只按 s->len + len 确保容量,可能导致末尾 \0 写越界。建议确保容量至少为 s->len + len + 1

Copilot uses AI. Check for mistakes.

s->len += len;
s->buf[s->len] = '\0';

return true;
}

bool ek_str_append(ek_str_t *s, const char *str)
{

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_append 直接对 str 调用 strlen(str);如果调用方传入 NULL 会产生未定义行为/崩溃。建议在这里对 str 做断言或按 ek_str_append_len 的约定将 NULL 视为 no-op。

Suggested change
{
{
// 按照 ek_str_append_len 的约定:传入 NULL 视为 no-op
if (str == NULL)
{
return true;
}

Copilot uses AI. Check for mistakes.
return ek_str_append_len(s, str, strlen(str));
}

bool ek_str_cat(ek_str_t *dst, ek_str_t *src)
{
return ek_str_append_len(dst, src->buf, src->len);
}

ek_str_t *ek_str_slice(const ek_str_t *s, int32_t start, int32_t end)
{
ek_assert_tiny(s != NULL);

int32_t len = (int32_t)s->len;

INDEX_CLAMP(start, len);
INDEX_CLAMP(end, len);

if (start == end) return ek_str_create("");

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_slice 在 clamp 后没有处理 end < start 的情况;此时 (end - start) 为负数,强转为 uint32_t 会变成超大值,导致巨量分配/越界读取。建议在计算 new_len 前显式处理 end <= start(返回空串或交换 start/end)。

Suggested change
if (start == end) return ek_str_create("");
if (end <= start) return ek_str_create("");

Copilot uses AI. Check for mistakes.

uint32_t new_len = (uint32_t)(end - start);

ek_str_t *new_s = ek_str_create(NULL);
if (new_s == NULL) return NULL;

new_s->buf = ek_malloc(new_len + 1);
if (new_s->buf == NULL)
{
ek_free(new_s);
return NULL;
}

memcpy(new_s->buf, s->buf + start, new_len);
new_s->len = new_len;
new_s->cap = new_len;

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_str_slice 分配了 new_len + 1 字节并写入终止符,但把 new_s->cap 设为 new_len,与其它地方把 cap 当作“可用 buffer 字节数(含 \0)”的用法不一致,会导致后续扩容判断异常(至少会多一次不必要的 realloc)。建议让 cap 与实际分配保持一致。

Suggested change
new_s->cap = new_len;
new_s->cap = new_len + 1;

Copilot uses AI. Check for mistakes.
new_s->buf[new_s->len] = '\0';

return new_s;
}

const char *ek_str_get_cstring(ek_str_t *s)
{
ek_assert_tiny(s != NULL);
if (s->buf == NULL) return "";
return s->buf;
}

uint32_t ek_str_get_len(ek_str_t *s)
{
ek_assert_tiny(s != NULL);

return s->len;
}

uint32_t ek_str_get_cap(ek_str_t *s)
{
ek_assert_tiny(s != NULL);

return s->cap;
}

int ek_str_cmp(ek_str_t *s1, ek_str_t *s2)
{
ek_assert_tiny(s1 != NULL);
ek_assert_tiny(s2 != NULL);

return strcmp(ek_str_get_cstring(s1), ek_str_get_cstring(s2));
}

int ek_str_ncmp(ek_str_t *s1, ek_str_t *s2, size_t n)
{
ek_assert_tiny(s1 != NULL);
ek_assert_tiny(s2 != NULL);

uint32_t len1 = ek_str_get_len(s1);
uint32_t len2 = ek_str_get_len(s2);

uint32_t shorter_len = (len1 <= len2) ? len1 : len2;

if (n > shorter_len) n = shorter_len;

return strncmp(ek_str_get_cstring(s1), ek_str_get_cstring(s2), n);
}

#endif // EK_STR_ENABLE
1 change: 1 addition & 0 deletions Test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ int main(void)
stack_test();
ringbuf_test();
vec_test();
str_test();

return 0;
}
56 changes: 56 additions & 0 deletions Test/str_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "test.h"

EK_LOG_FILE_TAG("str_test.c");

void str_test(void)
{
EK_LOG_INFO("str test start");

EK_LOG_INFO("make an empty str");
ek_str_t *s = ek_str_create(NULL);
EK_LOG_INFO("s:%s", ek_str_get_cstring(s));
EK_LOG_INFO("before free:%zu used:%zu", ek_heap_unused(), ek_heap_used());
ek_str_free(s);
EK_LOG_INFO("after free:%zu, used:%zu", ek_heap_unused(), ek_heap_used());

const char *i_am_a_string = "i am a string";
EK_LOG_INFO("make a str:%s", i_am_a_string);
s = ek_str_create(i_am_a_string);
EK_LOG_INFO("s:%s", ek_str_get_cstring(s));
EK_LOG_INFO("before free:%zu used:%zu", ek_heap_unused(), ek_heap_used());
ek_str_free(s);
EK_LOG_INFO("after free:%zu, used:%zu", ek_heap_unused(), ek_heap_used());

const char *hello = "hello";
EK_LOG_INFO("make a str:%s", hello);
s = ek_str_create(hello);
EK_LOG_INFO("s:%s", ek_str_get_cstring(s));
EK_LOG_INFO("str append");
ek_str_append(s, " world!");
EK_LOG_INFO("s:%s", ek_str_get_cstring(s));

const char *embeddekit = "EmbeddedKit";
EK_LOG_INFO("before append:%s", ek_str_get_cstring(s));
EK_LOG_INFO("append:%s", embeddekit);
ek_str_append_fmt(s, "%s", embeddekit);
EK_LOG_INFO("after append:%s", ek_str_get_cstring(s));

EK_LOG_INFO("slice \"%s\",at [12:%u] ", ek_str_get_cstring(s), ek_str_get_len(s));
ek_str_t *slice = ek_str_slice(s, 12, ek_str_get_len(s));
EK_LOG_INFO("slice:%s", ek_str_get_cstring(slice));
ek_str_free(slice);

const char *n1netynine99 = " N1netyNine99";
EK_LOG_INFO("cat test");
EK_LOG_INFO("make a new string:%s", n1netynine99);
ek_str_t *s1 = ek_str_create(n1netynine99);
EK_LOG_INFO("string:%s", ek_str_get_cstring(s));

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里刚创建了 s1 = ek_str_create(n1netynine99);,但日志打印的是 ek_str_get_cstring(s) 而不是 s1,会导致测试输出误导。建议改为打印 s1

Suggested change
EK_LOG_INFO("string:%s", ek_str_get_cstring(s));
EK_LOG_INFO("string:%s", ek_str_get_cstring(s1));

Copilot uses AI. Check for mistakes.
EK_LOG_INFO("cat start");
ek_str_cat(s, s1);
EK_LOG_INFO("cat result:%s", ek_str_get_cstring(s));

ek_str_free(s);
ek_str_free(s1);
EK_LOG_INFO("string finished,unused heap:%zu", ek_heap_unused());
EK_LOG_INFO("string finished,used heap:%zu", ek_heap_used());
}
2 changes: 2 additions & 0 deletions Test/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "../L2_Core/utils/inc/ek_ringbuf.h"
#include "../L2_Core/utils/inc/ek_vec.h"
#include "../L2_Core/utils/inc/ek_assert.h"
#include "../L2_Core/utils/inc/ek_str.h"

#define PI (3.141592f)

Expand All @@ -23,5 +24,6 @@ typedef struct
void stack_test(void);
void ringbuf_test(void);
void vec_test(void);
void str_test(void);

#endif
2 changes: 2 additions & 0 deletions ek_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* 模块功能开关
* - EK_EXPORT_ENABLE: 使能自动初始化
* - EK_IO_ENABLE: 使能IO库(基于lwprintf)
* - EK_STR_ENABLE: 使能字符串处理模块
* - EK_LOG_ENABLE: 使能日志模块
* - EK_LIST_ENABLE: 使能链表模块
* - EK_VEC_ENABLE: 使能向量模块
Expand All @@ -35,6 +36,7 @@
* ======================================================================== */
#define EK_EXPORT_ENABLE (0)
#define EK_IO_ENABLE (1)
#define EK_STR_ENABLE (1)
#define EK_LOG_ENABLE (1)
#define EK_LIST_ENABLE (1)
#define EK_VEC_ENABLE (1)
Expand Down