C标准库 memset 的一个问题

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


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

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

BOOL	BMP_get_Pixel_at( const BMP *image, const long row, const long col, PIXEL *pixel )
{
	int	height = BMP_get_Height( image );			//图像高度
	int	width  = BMP_get_Width( image );			//图像像素宽度
	int	pixelBytes	= BMP_get_PixelBits(image)/8;		//每像素字节数
	int	padwidth = (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>
typedef		RGBQUAD		PIXEL;
typedef	 struct ACB 
{
	int		a;
	char	b;
	int		c;
} ACB;
int main( int argc, char *argv[] )
{
	FILE	*fp = NULL;
	PIXEL	pixel;
	int		n;
	ACB		acb;
	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 );
	return	0;
}
为了节省空间,我省略了代码中的所有空行。

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


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

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


四、结论。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值