Undefined reference: memset_s
memset_s
未定义引用的根本原因
1. 你所使用的 libc 实现并不包含 memset_s
memset_s
是 C11 标准新增的安全函数,但并非所有实现都支持,例如:
环境 | 是否支持 memset_s |
---|---|
GNU libc (glibc) | 未实现(甚至到 glibc 2.39 都没有) |
Windows (MSVC CRT) | 支持 |
macOS (libSystem) | 支持 |
musl libc | 不支持 |
FreeBSD libc | 支持 |
编译器附带的内建实现 | 有时支持,有时不支持(依赖平台) |
📌 所以如果你在 Linux(glibc)上使用
memset_s
,就会链接失败,提示 undefined reference。
2. memset_s
是 C11 函数,需显式声明 __STDC_WANT_LIB_EXT1__
即便平台实现了 memset_s
,你也要在包含 <string.h>
前定义这个宏:
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
否则,memset_s
不会出现在头文件中。
3. 你误以为它是内建函数,但没有链接库实现
memset_s
不是编译器内建的,需要由标准库提供实现。如果你使用 gcc 或 clang,但所用平台(如 Ubuntu)用的是 glibc,就不会有它的实现。
错误示例(会编译失败):
#include <string.h>
int main() {
char buffer[100];
memset_s(buffer, sizeof(buffer), 0, sizeof(buffer)); // ❌ undefined reference
return 0;
}
错误信息:
undefined reference to `memset_s'
正确做法(跨平台兼容)
方法一:使用平台安全替代函数
平台 | 安全替代方案 |
---|---|
Windows | SecureZeroMemory |
Linux (glibc) | 使用 explicit_bzero 或 bzero |
自定义平台 | 自己封装一个安全 memset |
#if defined(_WIN32)
#include <windows.h>
#define secure_memset SecureZeroMemory
#elif defined(__APPLE__) || defined(__FreeBSD__)
#include <string.h>
#define secure_memset explicit_bzero
#else
#include <string.h>
static void secure_memset(void* v, int c, size_t n) {
volatile unsigned char* p = (volatile unsigned char*)v;
while (n--) *p++ = (unsigned char)c;
}
#endif
方法二:避免使用 memset_s
,自己封装一个兼容版本
static void portable_memset_s(void* dest, size_t destsz, int ch, size_t count) {
if (dest == nullptr || count > destsz) {
// 标准行为:返回错误码
return;
}
volatile unsigned char* p = (volatile unsigned char*)dest;
while (count--) {
*p++ = (unsigned char)ch;
}
}
总结
问题 | 说明 |
---|---|
memset_s 链接失败 |
因为 glibc 没有实现它 |
需要定义宏 | #define __STDC_WANT_LIB_EXT1__ 1 |
安全需求 | memset 可能被优化掉,memset_s / explicit_bzero 不会 |
兼容方式 | 自己封装 volatile 写法 |
explicit_bzero vs memset_s
explicit_bzero
与 memset_s
都是用于安全清除内存内容的函数,但它们的设计背景、使用语义、跨平台支持以及安全保证略有不同。下面我们从多个角度详细对比:
一览对比表
对比项 | explicit_bzero |
memset_s |
---|---|---|
所属标准 | 非标准(OpenBSD 起源) 被多个平台采纳 |
C11 标准扩展 (<string.h> ) |
是否可用 | ✅ macOS, BSD, Linux(glibc ≥2.25) | ❌ glibc 不支持 ✅ Windows 支持 |
防优化能力 | ✅ 明确保证不会被优化器移除 | ✅ 由标准要求不可优化 |
编译要求 | 无需宏定义,直接可用(若平台支持) | 需 #define __STDC_WANT_LIB_EXT1__ 1 |
错误处理 | 无返回值 | 有返回值(错误码) |
失败行为 | 没有错误机制(假设参数合法) | 会检查参数(如 null/越界)并返回错误码 |
安全级别 | 更适合做“内存清除”任务 | 更适合做“安全 memset”任务 |
C++ 可用性 | 可用(需要 extern “C” 或封装) | 可用(但可能链接失败) |
可移植性 | ❗较差(部分平台无) | ❗很差(glibc 无) |
详细对比说明
1. 安全语义(防止优化器移除)
函数 | 优化器会移除? | 保证方式 |
---|---|---|
memset |
✅ 可能移除 | ❌ |
explicit_bzero |
❌ 不会 | 明确设计为“不可优化” |
memset_s |
❌ 不会 | 标准要求“side-effect observable” |
📌 目的相同,但机制不同:
explicit_bzero
通常通过volatile
或外部函数实现,memset_s
要求编译器配合。
2. 返回值行为
explicit_bzero
:void
,不处理任何错误。memset_s
:返回errno_t
,有如下错误码:EINVAL
:空指针ERANGE
:count 超过 destsz 等等
这意味着:
explicit_bzero
是一种更简单的“安全清除”方式;memset_s
更适合被集成在具有防御性编程要求的系统里。
3. 可移植性
explicit_bzero
- ✅ FreeBSD, OpenBSD, macOS, glibc 2.25+,多数现代 Unix-like 系统支持;
- ❌ Windows 不支持(需手动实现)。
memset_s
- ✅ MSVC 实现了(Windows CRT);
- ❌ GNU glibc 至今 未实现(即使你定义了
__STDC_WANT_LIB_EXT1__
也没用); - ✅ 你可以自己实现,但不如
explicit_bzero
简洁。
4. 使用示例
explicit_bzero
#include <string.h>
char key[32] = "secret";
explicit_bzero(key, sizeof(key)); // 安全清除,无返回值
memset_s
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
char key[32] = "secret";
errno_t err = memset_s(key, sizeof(key), 0, sizeof(key)); // 返回错误码
5. 实现简洁性
explicit_bzero
是为“做清除这一个事情”而设计的,不带任何多余检查;memset_s
是标准化、类型安全、检查更多的版本,更加繁琐但更“现代安全”。
实际推荐场景
场景 | 推荐使用 |
---|---|
只想清除内存,避免敏感信息泄露 | explicit_bzero ✅ |
需要标准库接口,跨平台代码量大 | 封装 secure_memzero() ✅ |
在 Windows 上写安全代码 | memset_s ✅(MSVC)或 SecureZeroMemory ✅ |
项目需对参数错误做出反应 | memset_s ✅ |
推荐封装方式
void secure_memzero(void* ptr, size_t len) {
if (!ptr || len == 0) return;
#if defined(_WIN32)
SecureZeroMemory(ptr, len);
#elif defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
explicit_bzero(ptr, len);
#elif defined(__APPLE__) || defined(__FreeBSD__)
explicit_bzero(ptr, len);
#else
volatile unsigned char* p = (volatile unsigned char*)ptr;
while (len--) *p++ = 0;
#endif
}
总结
维度 | explicit_bzero |
memset_s |
---|---|---|
安全 | ✅ 强 | ✅ 强 |
错误处理 | ❌ 无 | ✅ 有 |
跨平台 | ❌ 一般 | ❌ 差(glibc 不支持) |
简洁性 | ✅ 强 | ❌ 略繁琐 |
推荐场景 | ✅ 敏感数据清除 | ✅ 高安全、参数可控逻辑 |