C memcpy_s 函数
最后修改:2025 年 4 月 8 日
内存安全在 C 编程中至关重要,而 memcpy_s 提供了比传统 memcpy 更安全的选择。本教程将深入介绍 memcpy_s,包括其语法、用法和优点。我们将通过实际示例演示带有边界检查的安全内存复制。理解 memcpy_s 有助于防止缓冲区溢出和其他内存相关漏洞,尤其是在关键应用程序中。
什么是 memcpy_s?
memcpy_s 函数是 C11 中引入的 memcpy 的一个带有边界检查的版本。它在缓冲区之间复制内存,同时验证目标缓冲区的大小。如果目标缓冲区太小或任何参数无效,该函数将返回一个错误。与 memcpy 不同,memcpy_s 有助于防止缓冲区溢出漏洞。它是 C11 附录 K 边界检查接口的一部分。
为什么使用 memcpy_s 而不是 memcpy?
memcpy_s 提供了 memcpy 所不具备的运行时检查。它会验证目标缓冲区大小是否与请求的复制操作匹配。这可以防止可能导致安全漏洞的缓冲区溢出。虽然并非普遍可用,但建议用于安全性关键的代码。该函数返回错误代码,有助于诊断内存安全问题。
基本的 memcpy_s 用法
本示例演示了带有边界检查的 memcpy_s 的基本用法。
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Safe copying";
char dest[20];
errno_t result = memcpy_s(dest, sizeof(dest), src, sizeof(src));
if (result == 0) {
printf("Copied successfully: %s\n", dest);
} else {
printf("Error %d: Copy failed\n", result);
}
return 0;
}
此代码使用 memcpy_s 安全地复制字符串。在复制之前,会检查目标缓冲区大小是否与源大小匹配。函数成功时返回零,失败时返回非零。即使复制失败,错误处理也能确保程序的稳定性。这种方法可以防止未经验证的 memcpy 用法中存在的缓冲区溢出漏洞。
处理复制失败
此示例显示了因目标空间不足导致 memcpy_s 失败时的正确错误处理。
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "This string is too long";
char dest[10];
errno_t result = memcpy_s(dest, sizeof(dest), src, sizeof(src));
if (result != 0) {
printf("Error %d: ", result);
if (result == ERANGE) {
printf("Destination buffer too small\n");
} else if (result == EINVAL) {
printf("Invalid parameters\n");
}
return 1;
}
printf("Copied successfully\n");
return 0;
}
此代码演示了如何处理 memcpy_s 的不同错误条件。目标缓冲区故意设置得太小。当目标缓冲区不足时,函数返回 ERANGE。其他可能的错误包括用于无效参数的 EINVAL。正确的错误处理使程序更加健壮和安全。
安全地复制结构体
此示例展示了如何使用 memcpy_s 安全地复制结构体。
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[30];
float balance;
} Account;
int main() {
Account acc1 = {1001, "John Doe", 1250.75f};
Account acc2;
errno_t result = memcpy_s(&acc2, sizeof(Account),
&acc1, sizeof(Account));
if (result == 0) {
printf("Account copied successfully\n");
printf("ID: %d, Name: %s, Balance: %.2f\n",
acc2.id, acc2.name, acc2.balance);
} else {
printf("Error copying account structure\n");
}
return 0;
}
此代码使用 memcpy_s 安全地复制整个 Account 结构体。在复制之前会验证目标缓冲区大小。该函数可确保在结构体复制过程中不会发生缓冲区溢出。这种技术对于复杂的数据结构特别有用。
部分缓冲区复制
此示例演示了使用 memcpy_s 进行安全的部分缓冲区复制。
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
int main() {
int src[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int dest[5];
// Copy first 5 elements (20 bytes)
errno_t result = memcpy_s(dest, sizeof(dest),
src, 5 * sizeof(int));
if (result == 0) {
printf("Copied elements: ");
for (int i = 0; i < 5; i++) {
printf("%d ", dest[i]);
}
printf("\n");
} else {
printf("Error copying partial array\n");
}
return 0;
}
此代码使用 memcpy_s 安全地复制整数数组的一部分。目标缓冲区大小被正确计算和验证。该函数可确保仅复制指定数量的字节。当处理数组切片时,此方法可以防止缓冲区溢出。
零长度复制保护
此示例显示了 memcpy_s 如何处理零长度复制请求。
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Test data";
char dest[20];
// Attempt zero-length copy
errno_t result = memcpy_s(dest, sizeof(dest),
src, 0);
if (result == 0) {
printf("Zero-length copy succeeded\n");
} else {
printf("Error %d: Zero-length copy failed\n", result);
}
return 0;
}
此代码测试了 memcpy_s 在零长度复制下的行为。该函数能够正确处理此边缘情况而不会出错。零长度复制是有效的操作,不会对内存产生任何更改。memcpy_s 在此情况下返回成功,同时仍然验证缓冲区的有效性。
使用 memcpy_s 的最佳实践
- 始终检查返回值:妥善处理所有可能的错误情况。
- 使用正确的缓冲区大小:确保目标缓冲区大小参数与实际缓冲区大小匹配。
- 启用 C11 扩展:在包含头文件之前定义
__STDC_WANT_LIB_EXT1__。 - 考虑可移植性:为没有附录 K 的系统提供备用实现。
- 验证参数:调用前检查 NULL 指针。
来源
本教程探讨了 memcpy_s 函数,展示了它相对于传统 memcpy 的优势。虽然需要稍多一些代码,但它为安全编程提供了必要的内存安全保证。在安全性关键的应用程序中,始终考虑使用带边界检查的函数。
作者
列表 C 标准库。