[讨论] memset加错误的数据类型,惹出另一个隐藏的数组越界

辛昕   2012-1-9 10:07 楼主
C语言允许直接操作内存的权限,但这经常会惹出麻烦。

我在这阵子就干了两票 数组越界导致程序错误,而且是隐藏得比较深的那种。

第一次不说了,比较简单,因为定义了一个8元的数组,因为后期修改,只要4元,别的地方,都采用宏定义替换,因此一顺改了过来,但因为有的地方一开始使用的是直接数,查的时候没有找全,所以,以后这个4元数组经常被穿出了4个字节,导致了一个看起来毫不相干的误判问题
——因为这个数组和那个数组都是静态数组,又偏偏是邻居。
——理论上来说,就算不是它与它为邻,也总有人与他为邻。

这一次的问题跟memset有关。memset这个函数可以直接填充内存单元,比起用for来初始化数组,很多时候我倾向于前者,但它有一个潜在的危险:
那就是,它的填充是以字节为单位进行填充的。
char a[8];
memset(a,0,sizeof(a));
这样写完全没问题。

但是,如果碰上非char型,就闯祸了。
int a[8];
memset(a,0,sizeof(a));//如果你试过了这样写,查完内存单元你会回来骂我......是的,它没问题。
它为什么没问题?
瞎猫撞上死耗子。

要不,你再试试这个:
memset(a,3,sizof(a));

我相信,你会看到一个你意想不到的数据。

当然,这个还不会引起数组越界,除了数字乱七八糟,没啥太严重的事情——这也是我昨天没想明白的事情。

我前天犯的一个错就跟这有关系。
但是,我想不明白的是,为什么我碰上的事情居然会让数组越界?

早上我回来又查了一下我的程序,那个让数组越界的数组是一个全局数组,我当时的犯错误是错把一个char型就够用的数组定义成int型。
这时,我忽然想到外部引用这个程序时,很可能又把它定义成了char型。
也就是说
xxx.c
int a[8] ={3.43.43.234。。。。。};

YYY.c
extern char a[8];


昨晚接着看C和指针。恰好看到一段内容,让我似乎明白了自己错在哪里。
书里说了这样一个内容。
一个变量只能被定义一次,这是必然的。但是,它可以被多处声明,比如声明为引用外部的。

但是,如果前后两次指定的链接属性不一样呢?
那么,将以 定义的时候为准。

类比的,我觉得,也许对于数据类型也是一回事。
我试了一下,果真如此。

我定义的时候是什么类型,不管以后在外部指定为什么别的类型,实际这个变量都是以定义时的为准。



强者为尊,弱者,死无葬身之地

回复评论 (11)

int a[8]; memset(a,0,sizeof(a));//如果你试过了这样写,查完内存单元你会回来骂我......是的,它没问题。它为什么没问题?瞎猫撞上死耗子。

 

--因为在很多的编译器实现中,会提前将内存清零,所以说只要不越界,人工清不清零,或部分清零,效果都是一样的.

--sizeof(a)是指组a的元素个数,而不是占用的字节数,要想实现编程者的本意,我觉得可这么写 sizeof(a) *sizeof(a[0],

如果嫌这样写烦,可以定义成宏,比如

 #define SizeOf(x)    sizeof(x)*sizeof(x[0])

这样

memset(a,0,SizeOf(a));//就OK了

[ 本帖最后由 能圈就圈 于 2012-1-10 15:21 编辑 ]
能力越大,责任越大;知道越多,未知更多
点赞  2012-1-10 15:10

回复 沙发 能圈就圈 的帖子

sizeof返回的怎么可能是元素个数呢????
强者为尊,弱者,死无葬身之地
点赞  2012-1-10 16:58
那我想当然了,这样的话
memset(a,3,sizof(a));
应该没啥问题呀?
能力越大,责任越大;知道越多,未知更多
点赞  2012-1-10 17:13

回复 4楼 能圈就圈 的帖子

memset函数是以字节为单位填充的。
 所以,如果这个数组不是字符型,就会出现这样的状况。
a是int型数组,本意是要使数组都成为3
结果却是 0x03030303    0x03030303 0x03030303,,,,, 全乱了 [ 本帖最后由 辛昕 于 2012-1-10 23:13 编辑 ]
强者为尊,弱者,死无葬身之地
点赞  2012-1-10 23:08

在VC6.0上的验证

  1. #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    int a[4];
    char i;

    memset(a,3,sizeof(a));

    for(i = 0;i < 3;i++)
    printf("%02x\n",a[i]);
    return 0;
    }

强者为尊,弱者,死无葬身之地
点赞  2012-1-10 23:22

回复 5楼 辛昕 的帖子

辛昕说得很对,是我又疏忽了,那我再出道题,编写一个类似memset的函数,能实现我们所想要的正确的结果?
能力越大,责任越大;知道越多,未知更多
点赞  2012-1-11 09:14

回复 7楼 能圈就圈 的帖子

从外头回来后,开始想了想。
一开始我的想法很简单,写一个形参为void *的函数。
然后在子函数里强转成char型指针,就可以算出它的数据长度。

可惜void *指针比我想的复杂,我没法把它当形参传入。
看了几个百度页,基本理解的地方是,void *指针只定义不分配内存——其实这是废话
谁告诉他们定义了一个指针以后就分配了内存?
最多的情形只是借一个已经分配好内存的变量——就是说定义好以后,给指针赋值这个变量的地址,那也只仅仅是接受一个分配好的内存(地址而已)。

假如说,在这里void*用不了,那接下去怎么玩,可就有点费解了。
——忽然想到,如果我在主调函数里传该指针的两个临近单元?
不过,这样一来,这函数就显得很弱智,很多余。
再想想吧~~
强者为尊,弱者,死无葬身之地
点赞  2012-1-15 02:30
这个题目也比我想象的要复杂,如果数组的类型是局限于char和int,那应该没啥问题,如果加上float啥的就不好办,C的函数都是传值调用,无法知道传进去的数是啥类型.
能力越大,责任越大;知道越多,未知更多
点赞  2012-1-15 17:45

回复 9楼 能圈就圈 的帖子

嘿嘿,这个,可能要另行设计一个 把float的二进制格式解释函数.......

不过,对于整型的,我还没想到咋弄。
强者为尊,弱者,死无葬身之地
点赞  2012-1-15 18:29
如果只是处理字符型和整型,应该还是比较简单的.
我试着写一下这个函数:

void MemSet(void *p,int c,size_t n,int size)
{
  if(size==sizeof(char))
    memset(p,c,n)
  else
   {
    int i = n / size;
    int *pi=(int *) p;
    whie( i-- )
     {
      *pi++=c;
     }
   }
}

调用的时候用如下方式:

int a[10];
char b[10];


  MemSet( a, 3, sizeof(a), sizeof( a[0] ) );
  MemSet( b, 3, sizeof(b), sizeof( b[0] ) );

不过,上述函数比真正的memset多了个参数.至于想做到和memset同样参数个数,
个人认为是实现不了的.
很容易,可以扩充到处理字符型,短整和长整.
能力越大,责任越大;知道越多,未知更多
点赞  2012-1-15 20:44

回复 11楼 能圈就圈 的帖子

额,我在我的电脑上试了试,居然编译不通过。
gcc编译器。

引用: NPP_EXEC: "C complier"
cmd /c "gcc -o E:\test\HEX E:\test\HEX.c "
Process started >>>
E:\test\HEX.c: 在函数‘_MemSet’中:
E:\test\HEX.c:24:3: 错误:expected ‘;’ before ‘{’ token
<<< Process finished.
================ READY ================

强者为尊,弱者,死无葬身之地
点赞  2012-1-20 23:34
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复