C语言指针及内存拷贝实践心得

1.背景

内存拷贝动作是非常常见的。例如考研408《操作系统》中将从外设接收到系统缓冲区的数据还需要拷贝到用户区。拷贝到用户区这一过程正是内存拷贝。

内存拷贝一般直接调用memcpy函数或者直接编写循环进行赋值。memcpy函数可以直接从C库中调用,在程序编写方面确实是高效率的。但是在实际运行当中,真的是最快的吗?

2.分析

memcpy的内部实现实际也是由两个指针赋值,并由循环控制偏移量,代码如下

void *memcpy(void *dest, const void *src, size_t count)

{undefined

if (NULL == dest || NULL == src || count <= 0)

return NULL;

while (count--)

*dest++ = *src++;

return dest;

}

不难看出memcpy实现的是字节拷贝,即一次拷贝一个字节。这个方式在16位和32位处理器中并不能完全返回处理器的性能。在不考虑字节对齐问题的前提下,16位和32位处理器一次最多可拷贝2个和4个字节的数据,故同样使用循环+指针赋值的方式进行拷贝,但是将指针强制转换成16位或32位数组的指针是否能提高速度呢?

3.实验

这里以正好在做的STM32播放器从解码区拷贝到缓冲区的拷贝为例子进行实验。

void Fill_SAI_Buff0(void)//缓冲区填充
{
	if(buff0_attribute==NULL){
		return;
	}
	for(uint16_t count=0; count<SAI_TX_BUFF_HALF/4; count++)
	{
		((uint32_t *)SAI_BUFF)[count] = ((uint32_t *)buff0_attribute)[count];
	}
}
void Fill_SAI_Buff0(void)//缓冲区填充
{
	if(buff0_attribute==NULL){
		return;
	}        
    memcpy(SAI_BUFF, buff0_attribute, SAI_TX_BUFF_HALF);	
}

两段代码,分别是使用memcpy和强制转换为32位数组的指针并循环赋值的方式进行从解码区到缓冲区的拷贝。其中buff0_attribute是解码区,SAI_BUFF是缓冲区。总共拷贝的字节数为SAI_TX_BUFF_HALF=4096 。

经过测量,memcpy的实现方式需要大约40us,强制转换为32为数组指针拷贝的方式需要大约10us。后者明显快于前者。测量方式为笔者此前的此篇文章:KEIL5调试计时,测量程序运行时间,适用于STM32\MK60\IM6U等基于Cortex-M架构处理器单片机_Fairchild_1947的博客-CSDN博客

4.注意

 4.1注意拷贝次数变化

强制转换为32位数组指针的拷贝方式确实是更快的,但是在操作时需要注意单次拷贝长度的变换将导致拷贝次数的变化,所以必须要注意循环次数上限的设置。例如上面举的例子,将8位数组指针强制转换为32位数组指针拷贝次数将减少4倍

4.2注意拷贝总长度

例如上面举的例子,将8位数组指针强制转换为32位数组指针,这个例子可以正常使用的前提是,8位数组需要拷贝的字节数正好为4的整数倍。若不是将可能导致访问越界,或丢数据。

4.3注意强制转换的使用细节

这也是笔者犯的一个很蠢的错误,借此机会和大家分享一下。

void Fill_SAI_Buff1(void)//缓冲区填充
{
	if(buff1_attribute==NULL){
		return;
	}	
	for(uint16_t count=0; count<SAI_TX_BUFF_HALF/4; count++)
	{
		((uint32_t *)SAI_BUFF)[count+SAI_TX_BUFF_HALF/4] = ((uint32_t *)buff1_attribute)[count];
	}
SCB_CleanDCache_by_Addr((uint32_t *)(SAI_BUFF+SAI_TX_BUFF_HALF), SAI_TX_BUFF_HALF);//Cortex-M7处理器CACHE回写模式时必须使用	
}

在第二个解码区拷贝到缓冲区时,由于缓冲区的地址是连续的,所以直接通过地址偏移偏移到对应位置开始拷贝。但是现在涉及到了强制转换。在实际编写时误将“(uint32_t *)(SAI_BUFF+SAI_TX_BUFF_HALF)”和"(uint32_t *)(SAI_BUFF)+SAI_TX_BUFF_HALF"混淆。前者是对(SAI_BUFF+SAI_TX_BUFF_HALF)进行强制转换,而后者是对(SAI_BUFF)强制转换并且加上偏移量SAI_TX_BUFF_HALF。其中SAI_BUFF为8为数组,且偏移量SAI_TX_BUFF_HALF的单位也是字节,故“(uint32_t *)(SAI_BUFF+SAI_TX_BUFF_HALF)”正确。而"(uint32_t *)(SAI_BUFF)+SAI_TX_BUFF_HALF"的偏移量其实是以32位(4字节)为单位的,这样一来,偏移量就错了,所以第二种写法错误。

猜你喜欢

转载自blog.csdn.net/Fairchild_1947/article/details/122313424