之所以 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

http://bytes.com/topic/c/answers/222894-sizeof-x-c-c

http://leonax.net/p/3037/why-memset-takes-int-as-its-second-parameter-instead-of-char/?replytocom=11817