memset 是 c/c++ 中的一个内存清洗函数。因为是直接修改内存中的字节值,所以更偏向于底层。这篇文章简单介绍下它的用法。
从字符串开始
这是 Visual Studio 对 memset 的介绍:
void *memset(
void *dest,
int c,
size_t count
);
其中:
参数 | 解释 |
---|---|
dest | 指向目标的指针。 |
c | 要设置的字符。 |
count | 字符数。 |
作用是将 dest 的前 count 个字符设置为字符 c。我们举一个非常简单的例子:
这个函数将 char 数组的前 4 位设置为 *
#include <memory.h>
#include <cstdio>
int main( void )
{
char buffer[] = "This is a test of the memset function";
printf( "Before: %s\n", buffer );
memset( buffer, '*', 4 );
printf( "After: %s\n", buffer );
}
输出如下:
Before: This is a test of the memset function
After: **** is a test of the memset function
char 和 ASC|| 码在某些情况下是通用的,所以这里也可以写成
memset( buffer, 42, 4 );
效果是一样的。
如果不是字符串呢?
上面只是 memset 最常用的用法。事实上,memset 的正确解释是:
转换值 c 为 unsigned char 并复制它到 dest 所指向对象的首 count 个字节。
每个 char 占用 1 个字节,所以 4 个字节就是 4 个 char,也就是 char 数组的前 4 个元素。这没什么问题。但如果把 char 换成 int 呢?
先定义一个两个元素的 c 数组。
count 溢出
int buffer[2];
for(int a:buffer) {printf("before a = %d\n",a);}
memset( buffer, 42, 4 );
for(int a:buffer) {printf("after a = %d\n",a);}
将数组 buffer 的前 4 个字节赋值为 42,也就是二进制下的 101010
,结果如下
before a = 0
before a = 0
after a = 707406378
after a = 0
707406378 的二进制为00101010 00101010 00101010 00101010
(方便分析,我在前面补了0,下同),原因是,每个字节有 8 位,每一位都赋值为 42.我们试试赋值前 5 个字节。
after a = 707406378
after a = 42
42本身是填不满一个字节的,所以前6个字节的时候,值就有变化了。
after a = 707406378
after a = 10794
10794 的二进制为00101010 00101010
,与预期也基本相符。
同样,memset 存在 count 大于 dest 的 size 的溢出可能性,这种行为的处理是未定义的。在我用的 TDM-gcc 9.2.0 中,全填充以后正常运行,不会报错。
memset( buffer, 42, 10 );
after a = 707406378
after a = 707406378
c 溢出
我们尝试赋值一个超过 8 位的int,也就是大于255(11111111
)
memset( buffer, 256, 4 );
结果如下:
after a = 0
after a = 0
256 的二进制表示为 1 00000000
,显然,memset 只取了低 8 位。
更安全的 memset_s
本来这段是要介绍 memset_s 的,但是我的编译器怎么也无法编译成功网上的代码。
起初我以为是编译器不支持 C++ 11,或者支持但没有打开。后来我看到这篇文章:https://blog.csdn.net/StoryZX/article/details/123866245
这类所谓的’安全’函数没最初是由微软( Microsoft )为 Windows 平台实现的,其官方名字为 Safe C Library,见其官网 ,这里有这些函数的详细介绍,以及函数实现的文件依赖图( Include dependency graph )。但是有很多组织机构是反对将这些纳入 C 标准库中的,尽管最终微软说服 C 标准委员会( C standard committee) 将这些函数加入附录 K 中,但是这些函数仍然不是标准库的一部分。这些’安全’函数从 C11 标准才开始支持,但似乎也仅限于 MSVC (微软的 VC 运行库)。以上,大概就能够解释为什么官方手册中给的示例程序在自己的 Linux 开发机中无法编译、运行,即便引入了 srting.h 头函数,即便你在程序中定义了文档中所说必须的宏,也还是会显示找不到 memcpy_s 函数的定义。如果你真的去查找了一遍,就会发现,string.h 文件中根本没有对应的这些函数。至此,你可以理解为,这一类所谓更安全的函数,是微软的 VC 运行库中的函数,对于其他平台,默认并不支持,当今强制推广这些安全函数的只有 Windows 平台。(啊这,微软写的,自己不得给自己捧场。)
在我的 VSCode 里,被兼容性 if 包裹的这部分代码是灰色的。
一旦去掉 ifdef,就会报没有定义的错误。
还好,我电脑上有安装 VS 2022,我会在下一篇文章将编译器更换为 MSVC,或者使用 VS 2022 继续运行代码。