购物车0种商品
IC邮购网-IC电子元件采购商城
深入浅出: C 与 C++ 中“结构直接复制”的探讨
(2011/10/25 9:27:00)

结构直接复制在 c 语言中使用的不多, 但在 c++ 却是一个热门,几乎每一本书,几乎每一个 c++ 程序员的面试都不可避免的提到结构直接复制的问题。我们现在来探讨结构直接复制在 c 与 c++ 有什么异同,先来看看一段简单的程序:



    typedef struct SA
    {
    char x;
    } A;

    void main (void) {
    A a1, a2;
    a1 = a2;
    }
复制代码


其中的 a1 = a2, 就是结构直接复制, 在 c 中,这种方法优劣如何呢?来看看Keil C 生成的汇编代码:



    14: a1 = a2;
    C:0x0D07 7808 MOV R0,#0x08
    C:0x0D09 7C00 MOV R4,#0x00
    C:0x0D0B 7D00 MOV R5,#0x00
    C:0x0D0D 7B00 MOV R3,#0x00
    C:0x0D0F 7A00 MOV R2,#0x00
    C:0x0D11 7909 MOV R1,#0x09
    C:0x0D13 7E00 MOV R6,#0x00
    C:0x0D15 7F01 MOV R7,#0x01
    C:0x0D17 120C2C LCALL C?COPY(C:0C2C)
复制代码
可以看到,仅仅一个字节的复制,竟然使用了大量的代码,效率及其低下。而这段代码与Keil C 中 memcpy 是一模一样的:




    15: memcpy(&a1, &a2, sizeof(A));
    C:0x0CF3 7808 MOV R0,#0x08
    C:0x0CF5 7C00 MOV R4,#0x00
    C:0x0CF7 7D00 MOV R5,#0x00
    C:0x0CF9 7B00 MOV R3,#0x00
    C:0x0CFB 7A00 MOV R2,#0x00
    C:0x0CFD 7909 MOV R1,#0x09
    C:0x0CFF 7E00 MOV R6,#0x00
    C:0x0D01 7F01 MOV R7,#0x01
    C:0x0D03 120C2C LCALL C?COPY(C:0C2C)
复制代码
这就是说,Keil C 中”结构直接复制” 简单的调用了 memcpy。 而 8051 既没有 DMA, 也没有 block move/copy 之类的复制指令,因此 memcpy 的效率不会很高。


现在我们来看看 C++ 下面的表现,编译器使用 Borland C++, 同样的代码,Borland c++ 显然聪明了很多,仅仅生成了两条 8086 汇编指令:
cs:026D 8A46FC mov al,[bp-04]
cs:0270 8846F8mov [bp-08],al

非常精简。 这样,没有什么比较性。我们现在稍微修改一下
typedef struct SA
{

char x[3];
} A;

此时,Borland C++ 同样生成了一大堆的代码:




    #TEST2#13:a1 = a2;
    cs:026D 8D46FC lea ax,[bp-04]
    cs:0270 16 push ss
    cs:0271 50 push ax
    cs:0272 8D46F8 lea ax,[bp-08]
    cs:0275 16 push ss
    cs:0276 50 push ax
    cs:0277 B90300 mov cx,0003
    cs:027A E8D31E call N_SCOPY@
复制代码



我们可以得出一个结论,borland c++
下”结构直接复制” 的效率也不过如此。





等等!结构(类)直接复制在 c++ 里大量的使用,难道没有一个更好的方法来实现么?比如:



a1.x[0] = a2.x[0];



a1.x[1] = a2.x[1];



a1.x[2] = a2.x[2];



或者,程序员只想复制其中一部分的数据,而不是像在 c 下用memcpy 全部复制?C++ 下有什么样的方法可以实现呢?




网友评论:回答刘工 9 楼的问题:

复制一个10,000字节的结构或者文件!
自己写for还是while ?不用,a1=a2; 一分钟不要,很简单就完成了
不错,a1 = a2 看上去是比 memcpy(&a1, &a2, 10000) 更简单,但是别忘了 struct 需要声明,先不必提动态文件缓冲区,来看看不同的文件复制:
memcpy(&p1, &p2, 10001);
memcpy(&p3, &p4, 10002);

刘工你打算声明多少个结构?

网友评论:向24楼的母亲致意!cao 刘前辈(冷漠)的全家!

既然刘前辈(冷漠)如此的不要脸,那么我也不会刘前辈(冷漠)讲什么风度。我会让我的小弟们专程上来问候刘前辈(冷漠)的母亲。

网友评论:自己不会,就去百度
1、星形网络拓扑结构: 以一台中心处理机(通信设备)为主而构成的网络,其它入网机器仅与该中心处理机之间有直接的物理链路,中心处理机采用分时或轮询的方法为入网机器服务,所有的数据必须经过中心处理机。 星形网的特点: (1)网络结构简单,便于管理(集中式); (2)每台入网机均需物理线路与处理机互连,线路利用率低; (3)处理机负载重(需处理所有的服务),因为任何两台入网机之间交换信息,都必须通过中心处理机; (4)入网主机故障不影响整个网络的正常工作,中心处理机的故障将导致网络的瘫痪。 适用场合:局域网、广域网。
2、总线形网络拓扑结构: 所有入网设备共用一条物理传输线路,所有的数据发往同一条线路,并能够由附接在线路上的所有设备感知。入网设备通过专用的分接头接入线路。总线网拓扑是局域网的一种组成形式。 总线网的特点: (1)多台机器共用一条传输信道,信道利用率较高; (2)同一时刻只能由两台计算机通信; (3)某个结点的故障不影响网络的工作; (4)网络的延伸距离有限,结点数有限。 适用场合:局域网,对实时性要求不高的环境。
3、环形网络拓扑结构: 入网设备通过转发器接入网络,每个转发器仅与两个相邻的转发器有直接的物理线路。环形网的数据传输具有单向性,一个转发器发出的数据只能被另一个转发器接收并转发。所有的转发器及其物理线路构成了一个环状的网络系统。 环形网特点: (1)实时性较好(信息在网中传输的最大时间固定); (2)每个结点只与相邻两个结点有物理链路; (3)传输控制机制比较简单; (4)某个结点的故障将导致物理瘫痪; (5)单个环网的结点数有限。 适用场合:局域网,实时性要求较高的环境。
4、网状网络拓扑结构: 利用专门负责数据通信和传输的结点机构成的网状网络,入网设备直接接入结点机进行通信。网状网络通常利用冗余的设备和线路来提高网络的可靠性,因此,结点机可以根据当前的网络信息流量有选择地将数据发往不同的线路。 适用场合: 主要用于地域范围大、入网主机多(机型多)的环境,常用于构造广域网络。

http://zhidao.baidu.com/question/80089729.html




网友评论:这两人前世骨头混一块了,唉

网友评论:这是咋回事呢?

网友评论:这个刘前辈就是以前的wxj1952和冷漠。

本来是一个纯技术贴,又被刘前辈wxj1952冷漠给搅了,虽然我已经很克制了。查看 刘前辈wxj1952冷漠 的历史,它们基本上最后都是用上各种下流的手段。不知道所长是不是挖了刘前辈wxj1952冷漠祖坟,以至于对所长这么大的仇恨。

看过所长的帖子,所长虽然孤傲,但是人品技术绝佳,这点相信大家有目共睹。我维护所长,并非我认识所长,只是敬佩而已,正如我曾维护 hotpower 一样。我不怕跌份,不会像所长那样放不下君子身价与小人缠斗,只能退走,任由小人肆意中伤。

所长的离去,真是可惜,还有 iceage, 算法方面比highgear 还高,c++ 更是令 highgear望尘莫及,可惜他们都太君子了。

网友评论:

谁说keil C51结构拷贝不能优化?!LZ自己不会优化,就以为所有的人都不会比自己更高明?真以为自己天下第一呢。

呵呵,让小辈我来教教你,LZ 睁大眼睛看清楚,俺是怎么实现优化的。把LZ的memcpy( )从282字节优化到了22字节。




一下子显得LZ这种探讨没了一点意义,纯粹是自己无知,什么“一字节复制使用了大量代码,效率极其低下。”还玩到了C++来解决keil C51效率低下的问题?
本想贬低他人,结果还是让keil 羞辱了一把。你当keil 这个老外德国人像你这么蠢?
1#
可以看到,仅仅一个字节的复制,竟然使用了大量的代码,效率及其低下.
这就是说,Keil C 中”结构直接复制” 简单的调用了 memcpy。
真像那么回事似地。我KEIL C调用了所长的memcpy( ) ? 谁调用谁呀,装腔作势似地。 你的memcpy( ) 能像结构复制这样优化? 你玩一个给俺瞧瞧,开开眼。别光顾着指责别人这不行,那不对;很简单的道理,你玩一个对的、漂亮的给我们菜鸟看看,做个模板让我们学习。别光凭嘴巴说,玩文化产业,忽悠我们菜鸟不懂 ?

唬得了一时骗不了长久 。骗到54 岁了还大忽悠呢。
13楼早就告诉你,结构拷贝可以寄存器优化,而memcpy()无法实现(用户实参指针,你能优化?好好动动脑子)。竟然没听懂别人说的是什么。

呵呵,所长“天下第一高手”这几天又要在家研究memcpy 的优化问题了。一定要和别人比较个高下?
累不累?


、、

、、

网友评论:别,这个是地址复制吧。

网友评论:小朋友真会开玩笑,拿魔术这一套来糊弄人



说白了就是自己写个类似头文件的东东拿来骗人的

网友评论:我承认我比较菜,我把Keil能更改的设置全改一遍,仍然得不到刘xx图示的结果,说人家PS的吧,无凭无据不能乱说。仔细看图片代码才发现问题的症结所在
我们编译器自带的C?COPY是插在Main函数之前的,而且不会有LOOP1这种指定地址的方式的,他的C?COPY函数为什么会在Main函数之后,
再看那个项目怎么比我们自己做实验多了个文件夹,结果就是那样简单,就像把刘谦的魔术放慢了仔细看,就知道他是怎么变出花样来了。
这种把戏拿来娱乐一下大家,炫耀一下自己是不错的
但拿来PK那就有点胜之不武了,不过对小孩子嘛,用不着上火动气,自会有家长管教的

网友评论:

代码开销:和复制1字节一样。282字节。当然时间可能不一样,C51拷贝什么都是逐字节来的。反正C51上感觉不到,和复制1字节一样快
-------------------------------
使用时钟周期/字节.你就知道是不是一样快了.
就是在c51上,优化还是有可能的.
-----------------------------------------------
不信你做一个32k字节的复制试试.
最快速的代码是最长的代码,最小的代码是最慢的代码
在高性能计算理论里,这叫做循环展开
------------------------------------------------
只会用for和while,却不会选移动操作的人,还是一边休息吧.
DMA快,但是是硬件层,这里不谈

网友评论:

34楼aihe,我来教你怎么优化。你得不到结果,不一定是别人唬你。如若谁都能做到,LZ就不会在这做这种探讨了。
首先282字节完全是由于?C?COPY这个通用的完整的库函数代码太长的缘故。它是通用的,无论是data——>XDATA,还是idata,xdata之间,都可以互相拷贝,没什么不好。编译器首先设好a1,a2,2个指针,和复制长度;然后调用?C?COPY函数。我想你当然知道,所长所说的“效率低下的大量代码”是干什么用的:

1、a2 指针,在R1(指针低8),R2(指针高8),R3(存贮区编码);
2、a1 指针,在R0,R4,R5;(对应上述 L,H, 编码);
3、复制长度:int 在R6,R7。

效率低下?为了调用 ?C?COPY,8个寄存器一个也不能优化掉。因为?C?COPY是库函数。它要求输入3个实参。
所以,要缩写代码,只能重写库函数?C?COPY。
既然我是固定存贮区idata之间复制10字节,我只要把idata 之间互相复制的代码加以利用就行了。其他的编译器都做了。
我重写了?C?COPY ,谁不知道项目库函数连接顺序可以自己设定?——把自己写的?C?COPY函数放在当前项目文件目录里就行了。
还用我把我写的?C?COPY 函数源代码给你看吗?

冤枉了别人,是不是应该有所表示?

很多时候,不是我们比别人高明,只是我们想不到或者没想到而已;别人一个套想了半年,我们可能想一辈子也想不到;所以,互相学习才好。
后面的结构复制 优化到22字节,更难想到了。所长弄出个什么一字节的结构复制!然后由此下结论:KeilC 不行,没他的C++好。下这种套?还C++ 解决?结果把自己绕进去了。

、、、、

网友评论:

我没冤枉你吧,是你重写了?C?COPY函数,不是德国佬怎么高明,其他编译器也可以这样做的,也不是你特别能耐,也不能证明Keil在这点上比其他编译器优越啊,
O(∩_∩)O哈哈~

网友评论:做硬件的不知道你们所云,呵呵淡定的飘过~

网友评论:

呵呵,aihe如若会重写库函数,早就想到了。那么也就不会有34楼的魔术疑问了。

还有,像那种“多了一个文件夹test”的问题,我看别人下面都是好几个文件夹,——那些都是文本文件或者是其它项目文件,可以随时换项目或者添加C文件的。


至于Keil C51 系统自带的?C?COPY 和memcpy( )比较的问题,所长已经一开始就证明了,2者代码完全一致,2者效率完全一样,都同样低下。自己把自己的memcpy( )否定了。面对事实,不得不。


aihe 还真有点特殊,你把memcpy( )实验也用同一个编译器做一下看,结果完全一样。8个寄存器设置,然后调用?C?COPY库函数。

如若懒得做,(怎么证明别人错的时候那么大精神,各种设置值都实验了一遍。)轮到证实自己错误的时候就懒得做了?咱们都是上海人,老乡,不会互相开打吧。
我帮你做一遍?

、、

网友评论:我呢是比较菜,但是我相信Keil自带的函数肯定是有他的道理,他们也不是白痴
开始我写函数的时候,看别人的函数都是太复杂了,偷偷得精简
不过到后来,考虑移植和通用,慢慢的写的和人家的就差不多了
说错话,做错事不要紧,别人指正时只要虚心接受,有则改之无则加勉就可以了
我才没事找人开打,不过那儿人都一样,只要保持住自己的心境,淡定淡定,O(∩_∩)O哈哈~

网友评论:这下可以看清第二个文件夹 test 里的内容了。上面的内容主要是自己的隐私文件名,不愿公开而已。



网友评论:

既然你这么愿意帮忙,那就帮我把前几个月写的一段程序再优化优化,注芯片是STM8

void EE_Wrait(unsigned int pSrc, unsigned int pDst,unsigned char ucLength) //写数据内置存储器
{ //从源地址写入到----目的地址----长度
unsigned char ucCount;
unsigned char *pDstAddr = (unsigned char *)pDst;
unsigned char *pSrcAddr = (unsigned char *)pSrc;
// 对数据EEPROM进行解锁
do
{
FLASH_DUKR = 0xae; // 写入第二个密钥
FLASH_DUKR = 0x56; // 写入第一个密钥
}
while((FLASH_IAPSR & 0x08) == 0); // 若解锁未成功,则重新再来

for(ucCount=0;ucCount<ucLength;ucCount++)
{
*pDstAddr = *pSrcAddr;
while((FLASH_IAPSR & 0x04) == 0); // 等待写操作成功
pSrcAddr ++;
pDstAddr ++;
}
}

网友评论:刘工,这就对了,你若技术讨论,那么就继续技术讨论;若继续污言秽语恶意中伤,那highgear就奉陪,继续问候你的家人。

刘工,优化 C?COPY 没有意义,无论再如何优化,keil c 下 a1 = a2 的效率都是很低。一个简单的一字节结构,仍然使用大量代码,优化 c?copy 失去了意义。而且,我一直强调, keil c 没有DMA 或 block Move 指令,因此,memcpy 的效率也不会比手工 c 代码高。

不必纠缠在 C?COPY 的优化上,此举对 a1 = a2 帮助不大。

网友评论:a1 = a2 在 keil c 下效率低下,而 Visual C++ 生成的代码却效率极高.

一字节结构:
a1 = a2;
004113B2mov al,byte ptr [a2]
004113B5mov byte ptr [a1],al

3 字节结构:

a1 = a2;
004113B2mov ax,word ptr [a2]
004113B6mov word ptr [a1],ax
004113BAmov cl,byte ptr [ebp-12h]
004113BDmov byte ptr [ebp-6],cl

8 字节结构:
a1 = a2;
004113BCmov eax,dword ptr [ebp-20h]
004113BFmov dword ptr [ebp-10h],eax
004113C2mov ecx,dword ptr [ebp-1Ch]
004113C5mov dword ptr [ebp-0Ch],ecx

12 字节结构:
a1 = a2;
004113BCmov eax,dword ptr [ebp-28h]
004113BFmov dword ptr [ebp-14h],eax
004113C2mov ecx,dword ptr [ebp-24h]
004113C5mov dword ptr [ebp-10h],ecx
004113C8mov edx,dword ptr [ebp-20h]
004113CBmov dword ptr [ebp-0Ch],edx

直至25字节以上,vc++ 才开始使用block move, 并未像 keil c 那样调用 memcpy.
a1 = a2;
0041354Cmov ecx,6
00413551lea esi,[ebp-48h]
00413554lea edi,[ebp-24h]
00413557rep movs dword ptr es:[edi],dword ptr [esi]
00413559movs byte ptr es:[edi],byte ptr [esi]

通过比较,可以这么猜测:结构复制在 c 不常用的原因,导致 keil c 并未对其优化;而 vc++ 中则因为结构复制的大量使用(c++中,结构是all public 的类),则特别优化,手工生成的代码也不过如此了。

网友评论:

如果对 c?copy 优化感兴趣,可以看看 keil c 论坛的帖子(我前面摘抄的英文源自于此):

http://www.keil.com/forum/6950/
http://www.keil.com/forum/8701/

Custom ?C?COPY ?
Bill Webster
I would dearly love to replace some of the Keil library functions that are used by intrinsics, such as ?C?COPY, which is called by memcpy (and by other things). My current goal is to generate runtime errors on certain types of copies, but replacement would be useful for other purposes.

I've actually written my own implementation of ?C?COPY, which works fine. The inline instructions are still emitted by the compiler and my version is linked.

However, I'm concerned about compatibility with future compiler releases.

I'm supposing that the interface can't change, because this would break older libraries, which could be third party.

But other library functionality may depend on something internal to the Keil ?C?COPY implementation, or may do so in future.

For my current purposes, I'm thinking of patching the entry to the Keil ?C?COPY after the code is built, to call my checking routine. This only requires that the interface doesn't change.

Has anyone else tried anything like this? Any comments on my assumption that the interface can't change?

PS - I know I can acheive my ends by writing my own memcpy and redefining memcpy() with a macro, but then heaps more code will be generated, because the calls will be far less efficient, without the special parameter passing.

网友评论:43楼aihe 的帮忙工作有没有效益?若是一般研究,我就交给学生做了。
小意思,可以到我家里来共同探讨。我请你吃饭。
我住静安。

网友评论:我等,程序盲,基本不会考虑效率问题,最直接的方法是升级处理器~~

网友评论:

2个相同算法之间相差几十个字节长度,根本无所谓效率问题,又不是核反应堆。
而且多数情况下,code 长度大(实际开销小)的那个算法更通用完善。

像LZ 指责keil C51 代码效率低,又臭又长,纯粹是无知。微软的Windows 臭不臭,长不长?LZ 优化一个给盖茨 看看?到微软应聘去吧。在这混,太屈才了。



、、

网友评论:

43楼 aihe:

学生交卷了,换了一种算法,code 缩减了一半多。——功能完全一样。
请你检验一下,有什么错误没有。如果正确,是不是应该表示一下?错了请指正。(我说他写的不错,如果证实,还得再次点名欣赏,可以就此强调一下概念。)

无论正/ 误,都可以让网友学习一下。

修改代码如下:
void EE_Wrait(unsigned intpSrc,unsigned intpDst,unsigned charucLength)
{
unsigned char ucCount;
unsigned charidata * pDstAddr =&pDst;
unsigned charidata * pSrcAddr =&pSrc;

do
{
FLASH_DUKR = 0xae;
FLASH_DUKR = 0x56;
}

while((FLASH_IAPSR & 0x08) == 0);

for(ucCount=0; ucCount < ucLength; ucCount++)
{
pDstAddr [ucCount] = pSrcAddr [ucCount] ;

while((FLASH_IAPSR & 0x04) == 0);
//pSrcAddr ++;
//pDstAddr ++;
}
}
code= 52 ; // 还要优化么?

原code=139 ;


//

网友评论:43楼 aihe老师的程序已是最优化的了,赞一个~~~

aihe老师注明芯片是STM8,真搞不懂,LS的大师怎么把51的 idata 存储器用于STM8? STM8芯片有 idata 存储器吗?

网友评论:学习了

网友评论:
43楼 aihe:

学生交卷了,换了一种算法,code 缩减了一半多。——功能完全一样。
请你检验一下,有什么错误没有。如果正确,是不是应该表示一下?错了请指正。(我说他写的不错,如果证实,还得再次点名欣赏,可以就 ...
刘前辈 发表于 2011-5-13 11:32
void EE_Wrait(unsigned intpSrc,unsigned intpDst,unsigned charucLength)
{
unsigned char ucCount;
unsigned charidata * pDstAddr =&pDst;
unsigned charidata * pSrcAddr =&pSrc;


pDstAddr =&pDst根本就是错误的,怎么改的?

网友评论:LS小六子,大师哪会错?

错的肯定不是小三就是小六~~~

网友评论:把你的程序替代,编译通不过

#error cpstm8 main.c:43(31+4) incompatible pointer types

我是这样调用的
EE_Wrait((unsigned int)&Sys_Set[0].Tim2_F,(0x4000+(((unsigned int)&Sys_Set[0].Tim2_F)-(unsigned int)&Sys_Set[0])),sizeof(Sys_Set[0].Tim2_F));
或这样
EE_Wrait((unsigned int)&Start_Set,0x4000,sizeof(Start_Set));

但是我发现我的代码并不是很优化,比另外的写法多占有空间,但是这样写很直观

网友评论:
    IAR STM8 1.2 直接使用__eeprom关键字访问EEPROM

    STM8L15X:
    #include "stm8l15x_conf.h"

    void __eeprom_write_8(unsigned short addr_eep,unsigned char data)
    {
    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    FLASH_Unlock(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep, data);

    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    FLASH_Lock(FLASH_MemType_Data);
    }

    void __eeprom_write_16(unsigned short addr_eep,unsigned short data)
    {
    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    FLASH_Unlock(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep, data/256);
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep+1, data%256);
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_Lock(FLASH_MemType_Data);
    }

    void __eeprom_write_32(unsigned short addr_eep,unsigned long data)
    {
    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    FLASH_Unlock(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep, (unsigned char)(data>>24));
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep+1, (unsigned char)(data>>16));
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep+2, (unsigned char)(data>>8));
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_ProgramByte(addr_eep+3, (unsigned char)(data>>0));
    FLASH_WaitForLastOperation(FLASH_MemType_Data);

    FLASH_Lock(FLASH_MemType_Data);
    }


    void __eeprom_write_many(unsigned short addr_eep,unsigned short size,unsigned short dummy,unsigned short addr_ram)
    {

    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    FLASH_Unlock(FLASH_MemType_Data);

    for(unsigned short i=0;i<size;i++)
    {
    FLASH_ProgramByte(addr_eep+i, *((unsigned char *)(addr_ram)+i));
    FLASH_WaitForLastOperation(FLASH_MemType_Data);
    }


    FLASH_Lock(FLASH_MemType_Data);

    }



    STM8S:
    #include "stm8s_conf.h"

    void __eeprom_write_8(unsigned short addr_eep,unsigned char data)
    {
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    FLASH_Unlock(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep, data);

    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    FLASH_Lock(FLASH_MEMTYPE_DATA);
    }

    void __eeprom_write_16(unsigned short addr_eep,unsigned short data)
    {
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    FLASH_Unlock(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep, data/256);
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep+1, data%256);
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_Lock(FLASH_MEMTYPE_DATA);
    }

    void __eeprom_write_32(unsigned short addr_eep,unsigned long data)
    {
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    FLASH_Unlock(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep, (unsigned char)(data>>24));
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep+1, (unsigned char)(data>>16));
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep+2, (unsigned char)(data>>8));
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_ProgramByte(addr_eep+3, (unsigned char)(data>>0));
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);

    FLASH_Lock(FLASH_MEMTYPE_DATA);
    }


    void __eeprom_write_many(unsigned short addr_eep,unsigned short size,unsigned short dummy,unsigned short addr_ram)
    {

    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    FLASH_Unlock(FLASH_MEMTYPE_DATA);

    for(unsigned short i=0;i<size;i++)
    {
    FLASH_ProgramByte(addr_eep+i, *((unsigned char *)(addr_ram)+i));
    FLASH_WaitForLastOperation(FLASH_MEMTYPE_DATA);
    }

    FLASH_Lock(FLASH_MEMTYPE_DATA);
    }


    主程序测试:
    volatile __eeprom __no_init char eep_u8;
    volatile __eeprom __no_init short eep_u16;
    volatile __eeprom __no_init long eep_u32;

    void main( void )
    {
    eep_u8=123;
    eep_u16=12345;
    eep_u32=123456789;

    while(1);

    }
复制代码

网友评论:

123jj先别太兴奋,不加idata一样。关键是躲过调用库函数。学生不过是移植到C51写的程序,连我也没玩过STM8。

小心最后自己打了自己的脸,让年轻学生弄个瞠目结舌不好看。尤其是女性。又是比他们年长20岁的女性。一个异型 MCU 你赢了他们二十几岁的学生很光彩是吧。“你们不懂吧……我就……” 呵呵,他们懂的东西,你这辈子入不了门。连皮毛都不可能懂。所长硬撑着都不懂,别说你了。

你还是专心注意力关心把自己的小车站起来为好。别老把眼睛盯着别人,跟所长似的,什么事非要和别人比个高下才兴奋?越比对手年龄越小了,现在和23岁的在校生对阵了?

学生说他再去看看STM8手册,我说不用,我一样给你优秀!研究生C语言你免考了。(你用不着把所有的MCU都去学会,你将来不是要打工的。你是做研究的,你以后的道路和别人不一样。)

有些人,干了一辈子,都在为别人打工。她什么类型的MCU都会一点,市场刷新太快,结果一生疲于奔命。其实我看这些自以为是的中年工程师,一辈子连51都学不好。会STM8?有用么?月薪最多10T而已。二踢脚,一生就这么过去了。姐姐还有发展前途?
学生23岁,我看他比你43岁写的程序强多了。

再告诉你和你尊重的所长,为什么51你们学一辈子也学不到头,看下面附件,……什么都知道点皮毛,然后就以为都会了。以为keil C效率低下?德国佬不过如此,缺少见识,看看德国佬干了什么,你技术生命不可能再学新技术了吧,现在知道再开始学也晚了……,这一生大概也只能玩玩小车了。

我还欠你10分,给不出去。

论坛上我就尊重2个人:殷文跃,XXX_xxx,他们看的懂我贴的是什么。他们知道KeilC51的分量,从来不在这方面轻浮装懂。知道自己一生该干什么。



、、

网友评论:谈不上兴奋不兴奋,反正俺C语言不懂,那几十条基本指令都写不全。

无知者无畏,俺早就不搞技术啦,怎么写程序?那是N年前的历史啦,已基本上忘个精光~~~

关于小车,前后匠人、老T叔撑着,后有 不光写程序小盆友跟着,哪里轮得上俺这个半吊子出手?

不过有一点刘小辈说对啦,俺的一生疲于奔命,养家糊口,买房养LP,还要供孩子留学。

一生就这么过去了。没有发展前途,彼此彼此,刘小辈也差不多如此吧,有能耐的人从不会把精力花在二姨家灌水!!!

至于这一生也只能玩玩小车?这一点刘小辈也说对啦,俺小车也玩不转,要依靠匠人、老T叔、highgear、不光写程序等等,这些网友大力支持,没有这些网友的大力支持,俺一天也玩不下去。。。。。。。因为对这小玩意儿提不起兴趣。

听说刘小辈几年前就玩小车?何不拿出来晒一晒,让俺们菜鸟开开眼见~~~

网友评论:

避开51data写一个。学生的长辈是中医专家,总是教育后辈,中医界尤其反感什么事还没做到的时候就吹,“包治百病”“手到病除”“攻克癌症”……(“移植”到论坛就是别人写程序还在调试阶段,就以为是最终结果了;就开始贬低他人不懂。认为别人很差劲……这也太着急了吧,高兴的太早了吧。……)中医界总是“我不能保证……容我试试……”。什么事给自己留后路。免得最后下不来台。——也写给所长,别坚持了几年 memcpy( ) ,实在下不来台,又换C++ 了。C++最后也不行,他还有C# ,还有JAVA。别到后来说胡话了。

#include<absacc.h>


void EE_Wrait(unsignedint pSrc, unsignedint pDst,unsigned char ucLength)
{
do
{
FLASH_DUKR = 0xae;
FLASH_DUKR = 0x56;
}
while((FLASH_IAPSR & 0x08) == 0);

while(ucLength--)
{
DBYTE[pSrc] = DBYTE[pDst];
while((FLASH_IAPSR & 0x04) == 0);
pSrc++;
pDst++;
}
}
一样,code=51字节.

什么?STM8 没有absacc.h ?! ——学生自己做的。


、、

网友评论:淡定,淡定
技术以外的东西大家去同僚老乡版面去侃
刘xx的程序我放到Keil中试过了,优化效果的确明显
我先说我程序的缺点:
1、把++,改为——,改判零,会再少一个字节
2、开锁用完后没把锁再锁上,易招贼,(误写入)
刘xx你程序的问题在于:看我的函数名和注释就知道这是一个用于写EEROM的程序,无论哪个单片机EEROM都不能用IDATA存取,不符合题意。

网友评论:

感谢老乡,59楼的程序改过了,可行?

把下面的DATA存储区说明修改成EEROM 区就行了。
DBYTE[pSrc] = DBYTE[pDst];

要不我拿个STC的EEROM 程序帮你改?反正都是学生作业,学生做完了还可以写论文;这种实战程序,评委都不懂的,只好给论文优秀。

有点错更好啊,BUG越多越好,查错才增长能力呢。一帆风顺学生论文还没内容可写。


//

网友评论:



实际效果出来了,你那个程序比我那个多用了20个CODE
因为我去掉了idata

在Keil下面如果去掉idata也会这样效果。。。
你自己试一下就知道了
我再重申一下,这个是写EEPROM的程序,没办法给你评优
你再想一下,如果数据长度是200,你你那个程序能正常运行吗?

网友评论:俺在51楼已说过了,aihe老师的程序已是最优化的了,有截图为证。

可惜,aihe老师不相信俺~~~
43楼 aihe老师的程序已是最优化的了,赞一个~~~

......

123jj 发表于 2011-5-13 12:11


网友评论:

62楼 aihe 朋友:没关系,小事一桩,很多方法,变通;
我原来不懂STM8,还不如我学生呢。学生说他查了一晚上资料就入门了。

1、你把下面的指针定义前面加上 @near ,也就是:

@near unsigned char *pDstAddr = (unsigned char *)pDst;
@near unsigned char *pSrcAddr = (unsigned char *)pSrc;

其它(你的程序)都不变,试试看什么效果;如果有效,一定告知。我好通知学生。——我实在不愿安装什么STM8开发环境。只能看看书了。这是把3字节指针化为2字节,编译结果按照C编译器规则,肯定简化。——这与Keil C51 使用idata 原理一样。

还有,书上都有的程序,我觉得还是参考书上的好;书上的标准程序是厂家提供的,时序上肯定测试多少遍了。咱们用户只管调用,恐怕不能随便化简吧。即使自己的程序好不容易通过,心里也没底。谁知道芯片时序是什么样?——换个环境程序又不灵了,怎么办?

下面是书上的程序,要写200个字节更简单了;利用成块写入功能,按照128字节一个单位块来写,——又快又简单。厂家提供的现成程序,一天就完成了。
不用自己下那么大工夫研究。

下面是厂家提供的按字节写EEROM操作标准程序。



需要成块编写程序请告知。有必要把电子书发给你。(我今天看了一上午就大概明白了。)
我觉得你应该也有。而且肯定参考过比我更详细的资料。——不明白为什么要自己写。我绝对写不过厂家——除非我在厂家做测试。
不过添加 @near 是我自己认为的,keilC实验出来的。

厂家程序写的真是无可挑剔,没有3字节指令定义的情况。——没地方需要加@near 。




、、

网友评论:真是的还不清楚吗?
@near和XDATA一样的意思
idata只能用在RAM的最前256字节,超过这个数字出现的结果无法预料?!
再说一下,哪个MCU的EEROM在idata的取址范围里?!

网友评论:

aihe你说得对,我的@near是加在你自己定义的指针pDstAddr前面的 ,仅仅是说明你这个指针是个指向xdata区域 数据的指针,它只需要2字节分配(如果不加,那编译器默认指针3字节指针。)STM8编译器把它分配在哪,是编译器的事,与用户无关,这2个指针指向EEROM地址 pDst ,或 pSrc。 和EEROM 读写的数值没关系,它只是个地址值,例如4000H。——它指向4000H,* pDstAddr 从4000H取数值,和pDstAddr 分配放在哪(xdata / idata ) 没有关系,这和所有C语言概念一样。
也许应该像下面这样:

unsigned char * @near pDstAddr = (unsigned char *)pDst ;

如果你希望分配在256字节RAM区内,应该用@tiny ,关键是@near 或者 @tiny 的位置。keil C很简单,如果形如
xdataint* idataptr;

说明ptr指向 xdata 区的 int ,而ptr在data 区。显然数据操作在Xdata区。 STM8我无法实验,看书上意思一样。

还有,我理解的没什么偏差,我看过书上的标准程序,没错。

你需要发给你看看,我以为你有。所以没贴。

@eeprom unsigned char * pDstAddr = (unsigned char *)pDst ;
unsigned char @near * pDstAddr = (unsigned char *)pDst ;

都可能对,但意义不一样。

等一等,我明白了。主要EEROM是什么区的问题?好像书上讲明了。
还有一点。请问aihe ,你的2个指针都指向EEROM? 那你的程序好像没通过?A,B俩个指针都指向EEROM,*A=*B;能行?可能还是要通过中间缓存。我认为至少pSrc不能是EEROM区。

@eeprom unsigned char* pDstAddr = (unsigned char *)pDst;
@near unsigned char *pSrcAddr = (unsigned char *)pSrc;

第二个@near 在64Kflash下是默认的,不用写也行。









、、

网友评论:*A=*B能行,但A[n]=B[n]就不一定行

你改的快,我在朋友家帮忙修东西,顺便腐败一下,没时间,截图都准备好了,只是没贴出来
你再看看我第二次说的,怎么调用这个程序,你就明白,我有可能从RAM写到EEROM,也有可能从FLASH写到EEROM,有可能是整个结构体写入,例如首次开机初始化,有可能只写1、2个字节用来修改部分参数还有可能接受外部的一次性修改,但是我并不需要从EEROM到EEROM,这个没必要

网友评论:

明白了。书上3种方式都有范例。明天贴给你,我觉得加入下面语句很重要。你没加是有原因?
/* 设置编程时间,FIX =1:编程时间固定为标准编程时间tprog。*/
FLASH_CR1 &= (unsigned char)(~0x01);
FLASH_CR1 |= 0x01;
A[n]=B[n]; 是要修改前面形参才可以的。例如:DBYTE 初始指向4000H而不是0000H。

2个指针不是同时指向EEROM就好办了。按书上来,和你写的也差不多,书上是按照存储区不同来区分EEROM的;例如写入4000H一字节就至少要3ms 。优化不优化关系不大。能缩减50个字节,效率一样。


、、

网友评论:编程时间在初始化中,不放在这里更好,你说呢?
还有无论@tiny放在哪里编译都通不过的。

在Keil中用idata只能在局限范围内使用,你试试看吧。

网友评论:

已经知道代码开销由指针引起,避免很容易:(EEROM段设置很简单)

void EE_Wrait(unsigned int pSrc, unsigned int pDst,unsigned char ucLength) //写数据内置存储器
{ //从源地址写入到----目的地址----长度
unsigned char ucCount;

// 对数据EEPROM进行解锁
do
{
FLASH_DUKR = 0xae; // 写入第二个密钥
FLASH_DUKR = 0x56; // 写入第一个密钥
}
while((FLASH_IAPSR & 0x08) == 0); // 若解锁未成功,则重新再来

for(ucCount=0;ucCount<ucLength;ucCount++)
{
*(unsigned char *)pDst = *(unsigned char *)pSrc;
while((FLASH_IAPSR & 0x04) == 0); // 等待写操作成功
pSrc ++;
pDst ++;
}
}

code 缩减1/3 ?——需要修改细节?从这里并看不出pDst是EEROM 区还是flash, Xdata RAM区。无所谓的,只要是指向004000H的指针就行。这是仿冯诺依曼结构,大家共64K/128K地址,读EEROM区和读 SRAM(Xdata)区一样指令?知道写入不一样就行了。


、、

然后讨论如何调用这个函数。


、、

网友评论:#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21(5) missing expression
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
#error cpstm8 main.c:21 bad character ?
main.c:
The command: "cxstm8 +mods0 +debug -pxp -no -pp -l -i"c:\program files\cosmic\cxstm8_32k\hstm8" -i"C:\Program Files\COSMIC\CXSTM8_32K\Hstm8"-clDebug\ -coDebug\ main.c" has failed, the returned value is: 1
exit code=1.

test.elf - 11 error(s), 0 warning(s)
编译还是不过
COSMIC编译要求很严的

网友评论:

很简单的调试:删去无关项,看看到底哪错:
void EE_Wrait(unsigned int pSrc, unsigned int pDst,unsigned char ucLength)
//写数据内置存储器
{ //从源地址写入到----目的地址----长度
unsigned char ucCount;

for(ucCount=0;ucCount<ucLength;ucCount++)
{
*(unsigned char *)pDst = *(unsigned char *)pSrc;
pSrc ++;
pDst ++;
}
}

上面是内存拷贝程序,与EEROM无关。若错就是上面写错了,很好查。

程序基本上是厂商提供的,如若错,肯定是我们自己哪不对。查吧,懒得查就放弃自己做。采用标准的。


bad character ,是出现了编译器不接受的字符,——是文字输入错误。很好检查。——不是程序语法错。

编译器标出哪行错?告知。还可以学习、修改一下:

如若你原来写的函数可以用,不如见好就收,你好不容易花时间调试出来的;我随便改也不好。改的反而不能用了,不讨好的事。
商家都提供了,看不出有什么问题。我不可能比商家写的更简单。

在我这怎么优化都是很简单的事,各种简约方法都很好玩。 要不我装一个COSMIC C 试试?意义不大。

下面是商家提供的程序:

unsigned int eeprom_address; /* 定义eeprom_address 变量 */

//定义EEPROM字节写 函数
void EEPROM_WRITE_BYTE ( unsigned int eeaddress, unsigned chareedata)
{
eeprom_address = eeaddress;

/* 设置编程时间,FIX =1:编程时间固定为标准编程时间tprog。*/
FLASH_CR1 &= (unsigned char)(~0x01);
FLASH_CR1 |= 0x01;

/* MASS密钥,解除EEPROM的保护 */
FLASH_DUKR = 0xAE;
FLASH_DUKR = 0x56;

*((unsigned char*) eeprom_address) = eedata;
//EOP=1,EEPROM编程结束
while((FLASH_IAPSR & 0x04) != 0x00);
}


看上去明显避开使用指针,很简约。


、、


、、

网友评论:*(unsigned char *)pDst = *(unsigned char *)pSrc
这个报错,呵呵
将地址转换成指针变量时必须强制转换
我觉得从整体考虑,优化函数的输入参数才是正事
算了也不要你改了,下次哪个网友有更好的方法告诉我一下

网友评论:

有道理。确实不严格。pSrc/pDst 是常量地址,编译器严格点就通不过了。

好办,稍微改改就通过了。——先设一个uint 变量pSrcAdrr ,不是指针;就行了。看厂商标准程序也是这么干的。


优化函数参数为指针,确实方便;不过code 就上去了。所以厂商也没那么干。——到底是要好的算法,还是要最小的Code 。可能STM8Flash 空间小,所以追求后一项。


、、

网友评论:别费那个劲了
就此打住吧

网友评论:void EE_Wrait(unsigned int pSrc, unsigned int pDst,unsigned char ucLength) //写数据内置存储器
{ //从源地址写入到----目的地址----长度
unsigned char ucCount;
unsigned int pDstAddr = pDst;
unsigned int pSrcAddr = pSrc;
// 对数据EEPROM进行解锁
do
{
FLASH_DUKR = 0xae; // 写入第二个密钥
FLASH_DUKR = 0x56; // 写入第一个密钥
}
while((FLASH_IAPSR & 0x08) == 0); // 若解锁未成功,则重新再来

for(ucCount=0;ucCount<ucLength;ucCount++)
{
*(unsignedchar *)pDstAddr = *(unsignedchar *)pSrcAddr;
while((FLASH_IAPSR & 0x04) == 0); // 等待写操作成功
pSrcAddr ++;
pDstAddr ++;
}
}

Keil C 通过。可能 pDstAddr ++; 会因硬件特殊报错。


//

网友评论:编译通过,截图为证,到此为止





网友评论:

我说以下几点,仅供讨论:
1. struct在c++中与class一样,是用来定义类的,它们之间的区别是: struct默认的成员属性是public的,class默认的是private.

2. A a1 = a2调用的是copy constructor. 而a1 = a2 调用的是assignment operator 函数(以下简称二个函数)。当你定义自己的类时,如果自己定义了这些函数,那我们没有争议,当然是调用这些函数。

3. 当我们没定义这二个函数时,编译器并不一定为我们提供这二个函数。提不提供要看你的类的定义具不具有 bitwise copy的语义。如果具有bitwise copy的语义,则编译器并不提供这二个函数,而是直接进行member-wise r 复制,而这种复制各种编译器的实现并不相同。如果不具有bitwise copy的语义,则会我们提供默认的这两二个函数。
4. 类具有以下四种特征时,则不具备bitwise 语义:
a. 当这个类有一个类成员,并且这个类成员显示的定义了这二个函数之一。
b. 当这个类继承的基类显示的定义了这二个函数之一。
c. 当这个类有虚函数的时候
d. 当这个类的继承链中存一个虚基类的时候。

以上四种情况存在时,则一个类的bitwise语义被破坏,这时编译器会提供上面提到的二个函数。除些之外,都是按照member-wise 来复制的,这个复制的实现各个编译器并不一定相同。

C++博大精深,学了十年,仍感觉是初学者。从来敢说自己会C++.也可能是用的少的原因

网友评论:“从来敢说”应是“从未敢说”,

网友评论:
编译通过,截图为证,到此为止

aihe 发表于 2011-5-15 19:26
aihe老师说的对,就此打住,再费那个劲,也不会有结果的,因为编译原理明摆着,优化是不可能超越基本硬件特征的。就算更改库函数,强制用汇编优化,也省不了几个字节,并且程序可移植性变差~~~

网友评论:

76楼学生写的太精彩了,包含了很多概念应用。最终结果并不在于一个小小的改动就把code缩减几十字节,主要是这里包含的教育意义。——什么叫直接寻址,间接寻址。能使用直接寻址的地方一定比使用间接寻址快。

aihe的源程序在keil C上 一个Xdata/idata就使程序代码缩减近100字节!这就是直接寻址和指针间接寻址的差别。——和keil C 还是 COSMIC C 根本没关系!

这年头,谁比谁差?Keil C 有Xdata/ideta ,COSMIC C 相应寻址就有@near / @tiny 。
C 编译器都是以ANSI C为标准的。论功能谁都不差。昨天是谁还在把 Keil C 贬的一无是处,现在是不是又该贬COSMICC 了。(在这种人眼里,除了自己,别人都是一无是处。—— 一天到晚以贬低别人为快乐。)以为意法 做不出个好的编译器?贬低意法也快乐 ?

谁说76楼学生修改的程序可移植性差?123jj 是想说:“意法的程序写的太差了,没有可移植性。”?
见图:



呵,别忘了学生这个仿COSMIC 源程序首先是在 keilC下通过的,什么意义不用提醒吧。—— ANSIC标准程序,这里没有idata , Xdata,不用那么仔细找茬。(不会揪住第一条说事吧,那我把它删了好了。)

贬低意法很快乐 ?

为何keilC加了一个xdata / idata 说明,就轻易把code 缩减了近 100字节?——COSMIC C理论上也一样;修改前后编译结果汇编码的分析比较是真正快乐的事;分析过程中所发现的东西,学了十年都不知道。

所以可以这么说,aihe 无意中的这段程序极有价值。几天学到的东西,胜读十年书。——照本宣科讲理论有什么意思,讲讲这段程序所包含的概念有意思多了。
像那种根本不会关注这种价值的分析研究过程的人,只关心什么“ keil 不算,这是STM8……”,呵,俺已经不关心这类处处给自己找台阶的事了,——仅从 keilC51 中发现的这种现象,才真正是让学生关注、兴趣投入的地方。
唉,赞叹:由此知道为什么 年长并没有用了。—— 有提升自己的好机会发现不了,——时间和生命全耗在 “头忙着打仗呢。”“正忙于寻找PK目标呢。”


、、








网友评论:LS刘小辈正解!

直接寻址一定比使用间接寻址快!

在少量数据移动时,直接寻址指令短一点,速度快一点,比间接寻址有着天然的优势!

但是,在大量数据移动时,直接寻址在速度上是能快一点,但代码庸肿,比间接寻址增加N倍代码!
并且几乎难以做到同类型数据之间的代码复制和移植!


这个问题根本不用讨论C语言还是C++,你只要用汇编编写一个移动复制1000个BYTE数据的代码,不充许用间接寻址,只能用直接寻址,那个代码量好吓人啊~~~

网友评论:在代码的压缩问题上,俺一向只看汇编,如果C编译后的代码,同俺手工写的差不多大小,俺就认为没有再优化的必要了~~~

网友评论:俺也累了,刘小辈,咱们听aihe老师之言
别费那个劲了
就此打住吧
aihe 发表于 2011-5-15 19:02


网友评论:淡定、淡定
在COSMIC上你那个程序和我那个最终的汇编代码是一样的
不知道intel做51时怎么想的,弄那么多的寻址方式,直接寻址就得了,还是Keil不够智能化,呵呵
转成指针时非得用三字节,还不存在Rx中,指针加1程序也搞得异常复杂
怪不得PIC等成为后起之秀呢
提醒一下自己,下次用这个程序时要小心了,如果FLASH+RAM+EEPROM超过64K时unsigned int是不够用的

网友评论:修正一下,超过32K,因为STM207 FLASH的起始地址是8080

浏览:(5488)| 评论( 0 )
博文评论

  • 昵 称:
  • 内 容:10~250个字符
  • 验证码: 验证码看不清楚?请点击刷新验证码
  •                      
  • 博文分类

    热点博文

    最新博文

    最新评论

    IC电子元件查询
    IC邮购网电子元件品质保障