C标准库 memset 的一个问题

来源:互联网 发布:数据分析师做什么 编辑:程序博客网 时间:2024/04/28 20:30

memset是C标准库的一个函数,其原型可以在string.h中找到。过多的介绍就不用了, 直接进入主题。


一、为什么断定是memset出错?

依然是数字图像处理的学习过程中遇到的一个问题。我在写BMP图像处理的时候, 考虑到BMP有灰度图像和彩色图像之分, 在BMP_get_Pixel_at函数中使用了memcpy和memset。代码如下:

BOOLBMP_get_Pixel_at( const BMP *image, const long row, const long col, PIXEL *pixel ){intheight = BMP_get_Height( image );//图像高度intwidth  = BMP_get_Width( image );//图像像素宽度intpixelBytes= BMP_get_PixelBits(image)/8;//每像素字节数intpadwidth = (width+3)/4*4;//填充后的像素宽度if( row>=height || col>=width ){printf( "错误:(%d,%d)数据越界!\n",row,col );return FALSE;}//从图像的像素位置读取pixelBytes字节数据,剩下的部分由0填充memcpy( (void *)pixel, (void *)( image->data+(row*padwidth+col)*pixelBytes ), pixelBytes );memset( (void *)(pixel+pixelBytes), 0, sizeof(PIXEL)-pixelBytes );return TRUE;}
其中 PIXEL 是Windows.h中的结构体 RGBQUAD 的一个别名,RGBQUAD的定义如下:

typedef struct tagRGBQUAD {  BYTE    rgbBlue;  BYTE    rgbGreen;  BYTE    rgbRed;  BYTE    rgbReserved;} RGBQUAD;
如上面的代码所示,int pixelBytes = BMP_get_PixelBits(image)/8; 保存图片的每像素的字节数。

既然这样,不管图像每像素字节数是多少,

memcpy( (void *)pixel, (void *)( image->data+(row*padwidth+col)*pixelBytes ), pixelBytes );
一句是可以读取一个像素的数据到pixel中的,尽管pixel的大小是4个字节。


显然,pixel的剩余部分要用0来填充。于是很理所当然的使用了

memset( (void *)(pixel+pixelBytes), 0, sizeof(PIXEL)-pixelBytes );
一句。

可以看见,位置偏移,数据数目都正确设置了。结果却出人意料,最后一句(指memset一句)却除了问题。

要知道,要定位到一个库函数出问题,是需要多门长时间的调试。反正,最终,问题被锁定在memset一句上面。大家可以看看锁定过程。


图1. memcpy执行时的情况。图中的箭头标明的是C代码对应的汇编计算过程。



图2. memset执行的过程。

图2中重点标出了memset出错的一句。按照上面的标示可以知道,edx就是pixel的地址,ecx是pixel的值。设想中,我们的目标地址pixel+pixelBytes,但是实际上的目标地址却是 pixel+pixelBytes*4。这就是memset出错的锁定点。


二、memset为什么要出错?

memset为什么要出错呢?我没有去证实,但是一个猜想是,依然是跟上一篇结构体中讨论的同一个理由:内存地址对齐!
本来pixel的地址是对齐了的(就算没有对齐,编译器也会自己做调整)。现在pixel增加了pixelBytes(好吧,我得说明一下,在我处理灰度图像的实验中,这个值是1),我猜想应该是四字节对齐(本篇的数据表明就是如此,上一篇的结果也是这个结论的有力证据)。pixel+1明显是非对齐的,而pixel+4是对齐的,所以memset想当然的把目标地址置换成了pixel+4,结果是,我们想改的地方没有修改到,而不想修改的地方却被意外修改。不幸的是,我在实验中 pixel+4的位置恰好是我的循环变量i的位置,所以每次获取像素的时候,总是可以将循环变量置为0。直接后果是,我的for循环就一直在那循环着……


三、另一个实验佐证

为了验证memset的对齐问题,我重新编写了一个小的测试代码,如下:
#include <stdio.h>#include <stdlib.h>#include <windows.h>typedefRGBQUADPIXEL;typedef struct ACB {inta;charb;intc;} ACB;int main( int argc, char *argv[] ){FILE*fp = NULL;PIXELpixel;intn;ACBacb;acb.a= 0x99;acb.b= 'a';acb.c= 0x9;fp = fopen( "acb.data", "w" );n = fwrite( (void *)&acb, sizeof(char), 1, fp );memset( (void *)&pixel, 23, 3 );memset( (void *)((&pixel)+3), 0, sizeof(PIXEL)-3 );n = fwrite( (void*)&pixel, sizeof(char), 2, fp );fclose( fp );return0;}
为了节省空间,我省略了代码中的所有空行。

诚如所料, 两个memset出现的问题如下图所示:


图3、继续测试中memset的对齐问题。

前面分析,pixel是对齐的,现在前面三个偏移被写入数据了,当我试图写第四个字节的数据的时候, 对齐发生了。离pixel+3最近的一个对齐地址是pixel+4,但程序选择的地址却不是这最近的一个,而是pixel+3*4。3即是我们视图偏移过去的量,可见memset在对齐的时候,采取的并不是最近对齐原则,而是采用我们的非对齐便宜乘以4作为对齐的偏移地址。


四、结论。

1、memset有对齐的问题;
2、memset的对齐偏移是我们试图偏移的非对齐偏移乘以4;
3、当我想使用memset设置一段内存数据的时候,必须使用非对齐偏移,应该怎么做?这个问题或许我会在后面的文章中提出解决办法,或者就一直在这里悬而未决!

原创粉丝点击