之所以 memset 函数的第二个参数为什么是 int 而不是 char, 只要有以下几个原因:1)为了兼容用字符常量对字符串或是字符数组的初始化(字符常量(如'a')在C语言中被认为成int类型);2)为了照顾已有的比较老的代码(C89标准之前);3)为函数调用,入栈;4)自动类型转换。
这篇博客是在一个好哥们的鼓励下写的。三个要好的朋友,有一个要走,我们为他践行。饭桌上讨论起了技术问题,其中有一个就是关于 memset 函数的第二个参数为什么是 int 而不是 char 。今天我就把我们所讨论的内容记下来(这让我想起了”小虎队“和《一路顺风》)。欢迎高手吐槽。
先给出 memset 的定义,linux-2.6.22.6/lib/string.c
/**
* memset - Fill a region of memory with the given value
* @s: Pointer to the start of the area.
* @c: The byte to fill the area with
* @count: The size of the area.
*
* Do not use memset() to access IO space, use memset_io() instead.
*/
void *memset(void *s, int c, size_t count)
{
char *xs = s;
while (count--)
*xs++ = c;
return s;
}
其中,函数的第二个参数为什么是 int 而不是 char ,而在函数内部,定义的是char * 类型的指针,这就导致,无论我们给 memset 传入的参数是1个字(char), 2个字节(比如short)或是4个字节(int),此函数都是一个字节一个字节地去填充s所指向的内存空间,即传入参数c只有最低字节有效。那么到这里,可能有很多人就会问,那为什么 此函数的第二个参数不用 char 而使用int 类型呢?这里给出三个原因,并给出例子。
我们通常这样使用 memset
int a[10];
memset(a, 0, sizeof(a)); // TODO: 这里最后一个参数是 40, 不是10
一) 先给出 memset 的另一种用法,对char数组赋值的 memset
char array[10];
memset(array,'a', sizeof(array) );
这里的 ‘a’ 占4个字节(你也许会感到奇怪,可事实就是这样),请看下面的例子。
1 #include <stdio.h>
2
3 void test(char a)
4 {
5 printf("%d\t", sizeof(a));
6 }
7
8 void main(void)
9 {
10 test('a');
11 printf("%d\t", sizeof(char) );
12 printf("%d\t", sizeof('x') );
13 printf("%d\n", sizeof(int) );
14 }
输出为:
1 1 4 4
C99标准的规定,'a'叫做整型字符常量(integer character constant),被看成是int型,所以在32位机器上占4字节。
即我们得出我们第一个结论,字符常量(如'a')存储在常量区,在C语言中也被认为成int类型,这可能是编译器为对齐而进行了自动填充。为了兼容用字符常量对字符串或是字符数组的初始化,于是memset的原型就只能也使用int了……
二) 为了照顾已有的比较老的代码,将 memse 第二个参数定义成 int 型。
老的C语言代码,据说在C89标准出来之前,C的代码中并不强制函数原型的声明,如果一个函数的调用出现在了它的声明之前,编译器会去假设一个声明。比如:
void bar() {
int a = foo(5);
}
int foo(int x) {
return x + 1;
}
在这段代码中,foo在bar中被调用,但是声明却在其之后,现在的编译器是会给出编译错误的,但是在C89之前,编译器会根据函数调用的语句,int a = foo(5)来猜出foo的函数原型,比如传入的值是5,就是一个int,返回值也是一个int。所以说,在一些古老的代码中,memse t的调用可以发生在它被声明之前。没有没有先声明函数的原型,则不能向函数传递char 类型的参数,如果这么做了,那么char 会被向上转换成int 类型,函数的返回值也会是int 类型。但在C89之后,函数声明变成了必须的,于是memset就一定要被先声明出来,但是为了照顾已有的比较老的代码,将 memse 第二个参数定义成 int 型。
三) 为函数调用,入栈(未最终确认,待高手指点)
在大多上平台上,一个 char 型的参数太小,不能被单独地压入栈中,所以通常将跟 char 大小相近的字的大小(也就是 int) 内容入栈。我个人觉得可能是这样,在函数调用的时候,如果传入的参数是 char, 编译器可能先会将其扩充至 int 型的大小,然后再传入参数(入栈)。
四) 自动类型转换。
假如改成 char 型,则我们不能传入 128~255 之间的参数(unsigned char 的范围),会溢出。同样,对于unsigned char ,我们也同样不能传入类似 -1 的参数 (signed char 的范围)。而我们定义为 int 型时,不管传入的是 char 型 还是unsigned char 型,都会向int 类型升级(promotion)转换。在向 memset 传入 int 类型数据时,只会最低字节来填充指定空间。
## 注意点
第二个参数只有0和-1可以。或者说,只有memset 0和-1会分别初始化为0和-1。对于其它的参数,我们要非常小心,要时刻铭记: memset是一个一个char来set的。0相当于0x00,-1相当于0xff,1相当于0x01。 因此, memset 0,每个元素都是0x00000000。 memset -1,每个元素都是0xffffffff。 memset 1,每个元素都是0x01010101,即16843009。
请看下面的几种填充方式,当第二个参数为 0, -1, 1, ’a' 的情况。
1 #include <stdio.h>
2 #include <linux/string.h>
3
4 int main(void)
5 {
6 int a[4];
7 char * p = (char*)&a[3];
8 memset(a, 0, sizeof(int));
9 memset(a+1, -1, sizeof(int));
10 memset(a+2, 1, sizeof(int));
11 memset(a+3, 'a', sizeof(int));
12
13 printf(" 0: ---> 0x%08x\t %d\t\t %u\n", a[0], a[0], a[0]);
14 printf(" -1: ---> 0x%08x\t %d\t\t %u\n", a[1], a[1], a[1]);
15 printf(" 1: ---> 0x%08x\t %d\t %u\n", a[2], a[2], a[2]);
16 printf("'a': ---> 0x%08x\t %c%c%c%c\t\t %u\n", a[3], *p, *(p<span style="font-family: Arial, Helvetica, sans-serif;">+1)</span>, *(p+2), *(p+3), a[3]);
17 }
输出为:
0: ---> 0x00000000 0 0
-1: ---> 0xffffffff -1 4294967295
1: ---> 0x01010101 16843009 16843009
'a': ---> 0x61616161 aaaa 1633771873
参考资料:
http://stackoverflow.com/questions/5919735/why-does-memset-take-an-int-instead-of-a-char