2017年1月

cpu为什么晶体管越多性能越强(CPU工作原理揭秘)

简单的说cpu就像是一个大的存放开关的工厂,每个晶体管就是一个开关,关的时候表示0,开的时候表示1,
晶体管越多,开关也越多,在处理同一个问题的时候走的线路也就越多。这就像是你以前学初中物理时的并联
电路,之路越多流通的线路也越多。同样,cpu的晶体管越多,单位时间内可以流过的电流的支路也就越多反
映在宏观上就是你在一颗cpu上能同时处理的数据也就越多,机器也就越快。
更现实生活一样,人多力量大
晶体管有的组成CPU内部数字开关,有的组成CPU内部的缓存;人多当然力量大,做的事情多,速度肯定快

CPU工作原理揭秘

众所周知,CPU是电脑的“心脏”,是整个微机系统的核心,因此,它也往往成了各种档次微机的代名词,如昔日的286、386、486,奔腾、PII、K6到今天的PIII、P4、K7等。回顾

CPU发展历史,CPU在制造技术上已经获得了极大的提高,主要表现在集成的电子元件越来越多,从开始集成几千个晶体管,到现在的几百万、几千万个晶体管,这么多晶体管,它们

是如果处理数据的呢?
◆ CPU的原始工作模式在了解CPU工作原理之前,我们先简单谈谈CPU是如何生产出来的。CPU是在特别纯净的硅材料上制造的。一个CPU芯片包含上百万个精巧的晶体管。人们在一块

指甲盖大小的硅片上,用化学的方法蚀刻或光刻出晶体管。因此,从这个意义上说,CPU正是由晶体管组合而成的。简单而言,晶体管就是微型电子电子开关,它们是构建CPU的基石

,你可以把一个晶体管当作一个电灯开关,它们有个操作位,分别代表两种状态:ON(开)和OFF(关)。这一开一关就相等于晶体管的连通与断开,而这两种状态正好与二进制中的

基础状态“0”和“1”对应!这样,计算机就具备了处理信息的能力。但你不要以为,只有简单的“0”和“1”两种状态的晶体管的原理很简单,其实它们的发展是经过科学家们多

年的辛苦研究得来的。在晶体管之前,计算机依靠速度缓慢、低效率的真空电子管和机械开关来处理信息。后来,科技人员把两个晶体放置到一个硅晶体中,这样便创作出第一个集

成电路,再后来才有了微处理器。 看到这里,你一定想知道,晶体管是如何利用“0”和“1”这两种电子信号来执行指令和处理数据的呢?其实,所有电子设备都有自己的电路和开

关,电子在电路中流动或断开,完全由开关来控制,如果你将开关设置微OFF,电子将停止流动,如果你再将其设置为ON,电子又会继续流动。晶体管的这种ON与OFF的切换只由电子

信号控制,我们可以将晶体管称之为二进制设备。这样,晶体管的ON状态用“1”来表示,而OFF状态则用“0”来表示,就可以组成最简单的二进制数。众多晶体管产生的多个“1”

与“0”的特殊次序和模式能代表不同的情况,将其定义为字母、数字、颜色和图形。举个例子,十进制位中的1在二进制模式时也是“1”,2在二进制位模式时是“10”,3是“11”

,4是“100”,5是“101”,6是“110”等等,依此类推,这就组成了计算机工作采用的二进制语言和数据。成组的晶体管联合起来可以存储数值,也可以进行逻辑运算和数字运算

。加上石英时钟的控制,晶体管组成就像一部复杂的机器那样同步地执行它们的功能。
◆ CPU的内部结构现在我们已经大概知道CPU是负责些什么事情,但是具体由哪些部件负责处理数据和执行程序呢? 1.算术逻辑单元ALU(Arithmetic Logic Unit) ALU是运算器的

核心。它是以全加器为基础,辅之以移位寄存器及相应控制逻辑组合而成的电路,在控制信号的作用下可完成加、减、乘、除四则运算和各种逻辑运算。就像刚才提到的,这里就相

当于工厂中的生产线,负责运算数据。 2.寄存器组RS(Register Set或Registers) RS实质上是CPU中暂时存放数据的地方,里面保存着那些等待处理的数据,或已经处理过的数据

,CPU访问寄存器所用的时间要比访问内存的时间短。采用寄存器,可以减少CPU访问内存的次数,从而提高了CPU的工作速度。但因为受到芯片面积和集成度所限,寄存器组的容量不

可能很大。寄存器组可分为专用寄存器和通用寄存器。专用寄存器的作用是固定的,分别寄存相应的数据。而通用寄存器用途广泛并可由程序员规定其用途。通用寄存器的数目因微

处理器而异。 3.控制单元(Control Unit)正如工厂的物流分配部门,控制单元是整个CPU的指挥控制中心;由指令寄存器IR(Instruction Register)、指令译码器ID

(Instruction Decoder)和操作控制器OC(Operation Controller)三个部件组成,对协调整个电脑有序工作极为重要。它根据用户预先编好的程序,依次从寄存器中取出各条指令

,放在指令寄存器IR中,通过指令译码(分析)确定应该进行什么操作,然后通过操作控制器OC,按确定的时序,向相应的部件发出微操作控制信号。操作控制器OC中主要包括节拍

脉冲发生器、控制矩阵、时钟脉冲发生器、复位电路和启停电路等控制逻辑 4.总线(Bus)就像工厂中各部位之间的联系渠道,总线实际上是一组导线,是各种公共信号线的集合,

用于作为电脑中所有各组成部分传输信息共同使用的“公路”。直接和CPU相连的总线可称为局部总线。其中包括:数据总线DB(Data Bus)、地址总线AB(Address Bus)、控制总

线CB(Control Bus)。其中,数据总线用来传输数据信息;地址总线用于传送CPU发出的地址信息;控制总线用来传送控制信号、时序信号和状态信息等。
◆ CPU的工作流程由晶体管组成的CPU是作为处理数据和执行程序的核心,其英文全称是:Central Processing Unit,即中央处理器。首先,CPU的内部结构可以分为控制单元,逻辑

运算单元和存储单元(包括内部总线及缓冲器)三大部分。CPU的工作原理就像一个工厂对产品的加工过程:进入工厂的原料(程序指令),经过物资分配部门(控制单元)的调度分

配,被送往生产线(逻辑运算单元),生产出成品(处理后的数据)后,再存储在仓库(存储单元)中,最后等着拿到市场上去卖(交由应用程序使用)。在这个过程中,我们注意

到从控制单元开始,CPU就开始了正式的工作,中间的过程是通过逻辑运算单元来进行运算处理,交到存储单元代表工作的结束。
◆ 数据与指令在CPU中的运行刚才已经为大家介绍了CPU的部件及基本原理情况,现在,我们来看看数据是怎样在CPU中运行的。我们知道,数据从输入设备流经内存,等待CPU的处理

,这些将要处理的信息是按字节存储的,也就是以8位二进制数或8比特为1个单元存储,这些信息可以是数据或指令。数据可以是二进制表示的字符、数字或颜色等等。而指令告诉

CPU对数据执行哪些操作,比如完成加法、减法或移位运算。我们假设在内存中的数据是最简单的原始数据。首先,指令指针(Instruction Pointer)会通知CPU,将要执行的指令放

置在内存中的存储位置。因为内存中的每个存储单元都有编号(称为地址),可以根据这些地址把数据取出,通过地址总线送到控制单元中,指令译码器从指令寄存器IR中拿来指令

,翻译成CPU可以执行的形式,然后决定完成该指令需要哪些必要的操作,它将告诉算术逻辑单元(ALU)什么时候计算,告诉指令读取器什么时候获取数值,告诉指令译码器什么时

候翻译指令等等。假如数据被送往算术逻辑单元,数据将会执行指令中规定的算术运算和其他各种运算。当数据处理完毕后,将回到寄存器中,通过不同的指令将数据继续运行或者

通过DB总线送到数据缓存器中。基本上,CPU就是这样去执行读出数据、处理数据和往内存写数据3项基本工作。但在通常情况下,一条指令可以包含按明确顺序执行的许多操作,CPU

的工作就是执行这些指令,完成一条指后,CPU的控制单元又将告诉指令读取器从内存中读取下一条指令来执行。这个过程不断快速地重复,快速地执行一条又一条指令,产生您在显

示器上所看到的结果。我们很容易想到,在处理这么多指令和数据的同时,由于数据转移时差和CPU处理时差,肯定会出现混乱处理的情况。为了保证每个操作准时发生,CPU需要一

个时钟,时钟控制着CPU所执行的每一个动作。时钟就像一个节拍器,它不停地发出脉冲,决定CPU的步调和处理时间,这就是我们所熟悉的CPU的标称速度,也称为主频。主频数值越

高,表明CPU的工作速度越快。
◆ 如何提高CPU工作效率既然CPU的主要工作是执行指令和处理数据,那么工作效率将成为CPU的最主要内容,因此,各CPU厂商也尽力使用CPU处理数据的速度更快。根据CPU的内部运

算结构,一些制造厂商在CPU内增加了另一个算术逻辑单元(ALU),或者是另外再设置一个处理非常大和非常小的数据浮点运算单元(Floating Point Unit,FPU),这样就大大加

快了数据运算的速度。而在执行效率方面,一些厂商通过流水线方式或以几乎并行工作的方式执行指令的方法来提高指令的执行速度。刚才我们提到,指令的执行需要许多独立的操

作,诸如取指令和译码等。最初CPU在执行下一条指令之前必须全部执行完上一条指令,而现在则由分布式的电路各自执行操作。也就是说,当这部分的电路完成了一件工作后,第二

件工作立即占据了该电路,这样就大大增加了执行方面的效率。另外,为了让指令与指令之间的连接更加准确,现在的CPU通常会采用多种预测方式来控制指令更高效率地执行。

寄存器的速度为何比内存更快?

转载自知乎 https://www.zhihu.com/question/20075426 侵删


作者:胡雨松
链接:https://www.zhihu.com/question/20075426/answer/16473901
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

Memory大致包含了一下几种器件。
锁存器(Latch)。锁存器是电平敏感的,一般来讲作为构成寄存器(register)一部分出现。在时序电路中不会单独出现,不然容易引起时序错误。
寄存器(Register)。寄存器是沿敏感的。1bit的寄存器也叫(触发器)Flip-flop。最常见的Flip-flop结构是这样的:

通常不算输出的话,一个Flip-flop是由16个晶体管(Transistor)组成的 @又见山人 。
Register的速度最快,消耗的晶体管也最多,所以一般作为时序电路的组成部分。所以ALU里面其实也是包含了register的。也有register专门用来储存数据的情况,比如说shift register array,但是它们的存储容量通常非常小。要知道几个kb的register的占用面积就是相当惊人的!
SRAM(Static Random Accessible Memory)。1bit由6个晶体管组成,一般作为片上存储器使用(On-chip-memory)。它消耗的晶体管比较多,但是速度快(仅次于register)。在CPU中作为cache的组成单元,大小是几MB到几十MB。Cache不是由register组成的, @时国怀 。
DRAM(Dynamic Random Accessible Memory)。一般1bit由一个晶体加一个电容组成。虽然面积小,但是速度慢,而且需要刷新。所以通常当内存用,存储几个GB的数据。
ROM(Read Only Memory)。这个就不说了。

================================以下是答案===============================
registers are closer to the ALU than memory 不靠谱。因为register本身就是memory的一种。这里即使当内存理解也不对。应该这么说,register本身的结构决定了它能比构成内存的DRAM工作在更高的时钟频率,所以才用在速度需求最快的地方(靠近ALU)。
另外,给register的时钟频率就是CPU工作的时钟频率,cache(无论一级二级还是三级)也和CPU同频。所以从理论上讲,cache和register是一样快的。不过因为cache里面的东西不一定就是需要的,如果不需要的话就要一级一级往下读取,实在找不着才往内存里面找。在往下级找数据的时候CPU是需要等待的。


又见山人芯片(集成电路)话题优秀回答者 集成电路工程师,…
56 人赞同
‘“registers are closer to the ALU than memory" 可以解释得通吗?或者说,需要有所补充?’

这么说也不是不可以,但这种说法真正的问题在于混淆了因果。做个类比就是,刘翔因为跑得快上了奥运会,不是因为他上了奥运所以跑得快。

从计算机体系结构角度而言,需要把不同速度和容量的memory分层级,得到效率和成本间较好的平衡。最需要经常访问的数据放在速度最快容量最小的寄存器和L1 cache里,访问量最少的数据放在最慢最大的内存条里,以此类推。

一个相当粗略和不精确的描述如下:

寄存器(register)经常自身就是CPU用的触发器,往往与CPU同时钟同频,当然是最快最方便的。但这玩意一个要20多个晶体管,多了芯片面积吃不消。

SRAM的优势在于速度较快,与一般半导体工艺兼容,因此被当作cache放在芯片内部离CPU近的地方,发挥其速度快的长处。但是这玩意存储密度小(一个bit要6个晶体管),放太多就贵了。

DRAM天生速度慢但存储密度高,正好适合做内存条这种东西。


时国怀 USB,驱动开发,RTOS
22 人赞同
"registers are closer to the ALU than memory"
可以认为基本上是对的,这里先来看看访问速度的比较:

一条汇编指令大概执行过程是(不是绝对的,不同平台有差异):

取指(取指令)、译码(把指令转换成微指令)、取数(读内存里的操作数)、计算(各种计算的过程,ALU负责)、写回(将计算结果写回内存),有些平台里,前两步会合并成一步,某些指令也不会有取数或者回写的过程。

再提一下CPU主频的概念:首先,主频绝对不等于一秒钟可以执行的指令个数,每个指令的执行成本是不同的,比如x86平台里汇编指令INC就比ADD要快,具体每个指令的时钟周期可以参考intel的手册。

为什么要提主频?因为上面的执行过程中,每个操作都需要占用一个时钟周期,对于一个操作内存的加法,就需要5个时钟周期,换句话说,500Mhz主频的CPU,最多执行100MHz条指令。

仔细观察,上面的步骤里不包括寄存器操作,对于CPU来说读/写寄存器是不需要时间的,或者说如果只是操作寄存器(比如类似mov BX,AX之类的操作),那么一秒钟执行的指令个数理论上说就等于主频,因为寄存器是CPU的一部分。

然后寄存器往下就是各级的cache,有L1 cache,L2,甚至有L3的,以及TLB这些(TLB也可以认为是cache),之后就是内存,前面说寄存器快,现在说为什么这些慢:

对于各级的cache,访问速度是不同的,理论上说L1 cache(一级缓存)有着跟CPU寄存器相同的速度,但L1 cache有一个问题,当需要同步cache和内存之间的内容时,需要锁住cache的某一块(术语是cache line),然后再进行cache或者内存内容的更新,这段期间这个cache块是不能被访问的,所以L1 cache的速度就没寄存器快,因为它会频繁的有一段时间不可用。

L1 cache下面是L2 cache,甚至L3 cache,这些都有跟L1 cache一样的问题,要加锁,同步,并且L2比L1慢,L3比L2慢,这样速度也就更低了。

最后说说内存,内存的主频现在主流是1333左右吧?或者1600,单位是MHz,这比CPU的速度要低的多,所以内存的速度起点就更低,然后内存跟CPU之间通信也不是想要什么就要什么的。

内存不仅仅要跟CPU通信,还要通过DMA控制器与其它硬件通信,CPU要发起一次内存请求,先要给一个信号说“我要访问数据了,你忙不忙?”如果此时内存忙,则通信需要等待,不忙的时候,通信才能正常。并且,这个请求信号的时间代价,就是够执行几个汇编指令了,所以,这是内存慢的一个原因。

另一个原因是:内存跟CPU之间通信的通道也是有限的,就是所谓的“总线带宽”,但,要记住这个带宽不仅仅是留给内存的,还包括显存之类的各种通信都要走这条路,并且由于路是共享的,所以任何请求发起之间都要先抢占,抢占带宽需要时间,带宽不够等待的话也需要时间。

以上两条加起来导致了CPU访问内存更慢,比cache还慢。

举个更容易懂的例子:

CPU要取寄存器AX的值,只需要一步:把AX给我拿来,AX就拿来了。
CPU要取L1 cache的某个值,需要1-3步(或者更多):把某某cache行锁住,把某个数据拿来,解锁,如果没锁住就慢了。
CPU要取L2 cache的某个值,先要到L1 cache里取,L1说,我没有,在L2里,L2开始加锁,加锁以后,把L2里的数据复制到L1,再执行读L1的过程,上面的3步,再解锁。
CPU取L3 cache的也是一样,只不过先由L3复制到L2,从L2复制到L1,从L1到CPU。
CPU取内存则最复杂:通知内存控制器占用总线带宽,通知内存加锁,发起内存读请求,等待回应,回应数据保存到L3(如果没有就到L2),再从L3/2到L1,再从L1到CPU,之后解除总线锁定。

这个链接是个图,可以看看CPU离主存(main memory)有多远,这个图没画寄存器,可以理解为:所有的L1 cache下面是寄存器,寄存器下面是CPU


达达lee 潜水/摄影/骑马/画画/开飞机/烹饪 以上都…
13 人赞同
转载一篇文章:为什么寄存器比内存快?

计算机的存储层次(memory hierarchy)之中,寄存器(register)最快,内存其次,最慢的是硬盘。

同样都是晶体管存储设备,为什么寄存器比内存快呢?

Mike Ash写了一篇很好的解释,非常通俗地回答了这个问题,有助于加深对硬件的理解。下面就是我的简单翻译。

原因一:距离不同

距离不是主要因素,但是最好懂,所以放在最前面说。内存离CPU比较远,所以要耗费更长的时间读取。

以3GHz的CPU为例,电流每秒钟可以振荡30亿次,每次耗时大约为0.33纳秒。光在1纳秒的时间内,可以前进30厘米。也就是说,在CPU的一个时钟周期内,光可以前进10厘米。因此,如果内存距离CPU超过5厘米,就不可能在一个时钟周期内完成数据的读取,这还没有考虑硬件的限制和电流实际上达不到光速。相比之下,寄存器在CPU内部,当然读起来会快一点。

距离对于桌面电脑影响很大,对于手机影响就要小得多。手机CPU的时钟频率比较慢(iPhone 5s为1.3GHz),而且手机的内存紧挨着CPU。

原因二:硬件设计不同

苹果公司新推出的iPhone 5s,CPU是A7, 寄存器有6000多位(31个64位寄存器,加上32个128位寄存器)。而iPhone 5s的内存是1GB,约为80亿位(bit)。这意味着,高性能、高成本、高耗电的设计可以用在寄存器上,反正只有6000多位,而不能用在内存上。因为 每个位的成本和能耗只要增加一点点,就会被放大80亿倍。

事实上确实如此,内存的设计相对简单,每个位就是一个电容和一个晶体管,而寄存器的设计则完全不同,多出好几个电子元件。并且通电以后,寄存器的晶体管一直有电,而内存的晶体管只有用到的才有电,没用到的就没电,这样有利于省电。这些设计上的因素,决定了寄存器比内存读取速度更快。

原因三:工作方式不同

寄存器的工作方式很简单,只有两步:(1)找到相关的位,(2)读取这些位。

内存的工作方式就要复杂得多:

(1)找到数据的指针。(指针可能存放在寄存器内,所以这一步就已经包括寄存器的全部工作了。)

(2)将指针送往内存管理单元(MMU),由MMU将虚拟的内存地址翻译成实际的物理地址。

(3)将物理地址送往内存控制器(memory controller),由内存控制器找出该地址在哪一根内存插槽(bank)上。

(4)确定数据在哪一个内存块(chunk)上,从该块读取数据。

(5)数据先送回内存控制器,再送回CPU,然后开始使用。

内存的工作流程比寄存器多出许多步。每一步都会产生延迟,累积起来就使得内存比寄存器慢得多。

为了缓解寄存器与内存之间的巨大速度差异,硬件设计师做出了许多努力,包括在CPU内部设置缓存、优化CPU工作方式,尽量一次性从内存读取指令所要用到的全部数据等等。


全面解析C语言中可变参数列表

在上一篇blogLinux内核源码分析--文件系统(三、buffer.c)中最后第二个函数struct buffer_head * breada(int dev,int first, ...) ,里面涉及到可变参数列表,所以就干脆来总结下可变参数列表问题。

大众版

首先来看下怎么使用,然后再来总结下其中一些问题:

 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int num, ...)
 {
     int i, result = 0;
 
     va_list ap;//这里写的什么list,(搞得好像是得到可变参数列表头一样)其实它就是个字符指针:char * 
     va_start(ap, num);// 这里把上面得到的字符指针,后移动4个字节,就是跳过num的内存地址
     printf("num:%d, *ap:%d\n", num, *ap);// 这里打印下就会看出,*ap 跳过了num指向了下一个参数
     
     for (i = 0; i < num; i++)//这里num表示可变参数列表中有多少个参数(num本身算不算,由自己觉得,这里是不算入参数个数的)
     {   
         result = va_arg(ap, int);//这里把ap往后跳过4个字节(sizeof(int)大小)指向下一个参数,返回的是当前参数(而非下一个参数)
         printf("in for  result:%d,  *ap:%d\n", result, *ap);//这里打印下,可以看出,ap总是指向result后面的那个参数
     }   
     va_end(ap);//结束标志
 
     return result;
 }
 //下面是测试函数
 int main()
 {
     int i = 4, j = 1, k = 2, g = 3, z = 4, m = 10; 
     printf("result:%d\n", test(i, j, k, g, z, m));
     return 0;
 }

看完后估计大家都会觉得很简单,这是大众版的,就是可变参数列表中第一个参数用来表示可变参数列表有多少个(至于算不算上他自己,那就看你程序自己设计了);

真实版

下面来看下真实版的程序:


 #include<stdio.h>
 #include<stdarg.h>
 
 int test(int first, ...){
 
     va_list args;
     va_start(args, first);
     printf("args:%d  first:%d\n", *args, first);
 
     while( (first = va_arg(args, int)) >= 0 ){
         printf("*args:%d  first:%d\n", *args, first);
     }   
     va_end(args);
     return 0;
 }
 int main()
 {
     int a = 100, i = 1, j = 2, k = 3, g = -1; 
 
     printf("test1:\n");
     test(a, i, j, k, g); 
 
     printf("test2:\n");
     a = 200, i = 11, j = 12, k = 13; 
     test(a, i, j, g); 
 
     return 0;
 }

这个和上面是一样的,唯一不同的是可变参数列表的第一个参数,没有用来当作参数个数,而是把最后一个参数用负数作为结束标志,可变参数列表第一个参数在这里的作用仅仅是为了得到可变参数列表的起始地址;(貌似这个程序上面的那个有点冗余,但是对照这个程序兴许会更好理解可变参数列表的应用)

实际原理

可变参数列表的实现是由几个宏组成的,在文件include/stdarg.h(我看的源码是0.11版本的,但是上面编译是在2.6版本内核上的,根据编译运行得到的结果,可以推理出可变参数列表实现程序在这两个版本中是一样的,也就是说两个版本内核中可变参数列表代码是相同的)中:
va_list 定义某个变量,其本质就是:

typedef char *va_list;//字符指针类型

va_start(ap, type)开始获取可变参数列表中的第一个参数(...里面的第一个),也就是跳过第一个参数(这里的第一个是num或first)

#ifndef __sparc__
#define va_start(AP, LASTARG)                         \
 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))//ap指向下一个参数,lastarg不变
#else
#define va_start(AP, LASTARG)                         \
 (__builtin_saveregs (),                        \
  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG))) //跳过下第一个参数,指向第二个参数内存地址
#endif

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

va_arg(args, int)循环获取到可变参数列表中的参数,args指向下一个参数地址,返回的则是当前参数地址

//  first=va_arg(args,int)
#define va_arg(AP, TYPE)                        \//ap指向下一个类型的参数
 (AP += __va_rounded_size (TYPE),                    \//返回ap - sizeof(type)参数,即前一个参数
  *((TYPE *) (AP - __va_rounded_size (TYPE))))

//对type向上取整 取int的整 4,然后乘上int整型4的倍数
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

最后一个va_end(ap)结束标志(没有好像也没关系),可能只是在程序中作为一个可变参数列表的结束标志而已(stdarg.h里面只是仅仅定义了下,没有实现的代码部分)。从上面的分析就可以知道第二个程序(所谓的真实版)是怎么来的了,其实根据实现原理来讲第一个参数是没有规定表示可变参数个数的,而是用来得到可变参数列表的起始地址。

自己实现可变参数列表

如果看懂了上面的可变参数列表原理,那么自己动手写个可变参数列表程序解析下多个参数问题,其实很简单的:

#include<stdio.h>//这里不需要用到可变参数列表解析宏,所以不用包含stdarg.h头文件
 
 int test(int first, ...)
 {
     int count = 0;
 
     char *ap ;
     ap = (char*)(&first);//得到参数列表的起始地址
 
     ap = ap + 4;//加上4跳过第一个参数first
     while(count++ < first){//这里first参数表示有多少个参数
         printf("*ap:%d\n", *((int *)ap));//把参数列表中的参数都挨个打印出来
         ap += 4;//指向下一个参数
     }   
     return *ap;
 }
 //下面是测试程序
 int main(void)
 {
     int i = 1, j = 2, k = 3, z = 4, num = 4;
     printf("test1:\n");
     test(num, i, j, k, z); 
 
     num = 3;
     printf("\ntest2:\n");
     test(num, i, j, k); 
     return 0;
 
 }

可变参数列表缺陷

不知道大家有没有发现,上面的所有可变参数都是int型的,就算你用字符作为参数(char  c = 'a'),在可变参数里面(三个小点里面)也同样会为他分配4个字节地址空间;如果是浮点型那么就会报错,至于具体为什么报错我还没有去看那三个点的实现(就是可变参数列表内存分配问题,我估计在库文件里面,后期看到再分析下);为什么不能用其他类型(非整型)的参数呢?我估计是开始设计的时候,为了简便,统一用整型(这样更好实现嘛),可是现实中这样就存在一个缺陷问题,那怎么解决让可变参数列表中可以使用浮点型、字符串呢?
答案是我也不知道,我只是想到了个方案,用第一个参数来表示可变参数列表中的类型,当然不能用int型了,而是用字符数组来表示,这也有个限制那就是只能有一个字符串而且是放在最后(当然要传多个字符串也可以在数组中自己设置),因为字符串长度不知道,指针不知道偏移多少位,所以把它放在最后就好了。
#include<stdio.h>
 
 int test(char num[], int i, char c, float f, char *s)
 {
     int ti;
     char tc;
     float tf;
     char *ts;
 
     printf("int:%p, char:%p, float:%p, char:%p\n", &i, &c, &f, &s);
 
     int count = 0;
 
     char *ap ;
 
     ap =  num + sizeof(num);
 
     while(num[count] != '\0'){
         switch(num[count++]){
 
             case 'i':
                 ti = *((int *)ap);
                 ap += sizeof(int);
                 printf("ti:%d\n", ti);
                 break;
             case 'c':
                 tc = *ap++;
                 printf("tc:%c\n", tc);
                 break;
 
             case 'f':
                 tf = *((float*)ap);
                 ap += sizeof(float);
                 printf("tf:%f\n", tf);
                 break;

             case '1':
                 ts = ap;
                 printf("ts:%s\n", ts);
                 break;
 
             default:
                 printf("No the type!\n");
         }
     }
 
     return 0;
 }
 
 int main(void)
 {
     int i = 12;
     char c = 'a';
     float f = 5.20;
     char *s = "yuzhihui";
 
     char num[4] = {'\0'};
      num[0] = 'i';
      num[1] = 'c';
      num[2] = 'f';
      num[3] = '1';
 
     test(num, i, c, f, s);
 
     return 0;
 
 }

上面就是我设计的一个雏形,大概意思就是这样,但运行时得不到正确结果,原因很有可能是可变参数列表中出问题了。因为...可变参数列表其在内存中的地址是连续的,而我上面的却不一定是连续的(模拟的可变参数列表),还有字节之间对齐问题(因为整型刚好是4个字节对齐的不存在这个问题).但大概意思就是这样的。
这个缺陷的改变可能并没有太多实际意义,我也是突发奇想,高手轻喷。哈哈
如果有什么不正确之处,欢迎大家指正,一起努力,共同学习!!
原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/43734663

bzero, memset ,setmem 区别

bzero

原型:
extern void bzero(void *s, int n);

用法:
#include <string.h>

功能:置字节字符串s的前n个字节为零。
说明:bzero无返回值。
举例:

// bzero.c            
#include <syslib.h>
#include <string.h>
int main()
{ 
        struct
        {
              int a;
              char s[5];
              float f;
        } tt;
        char s[20];
        bzero(&tt,sizeof(tt));   // struct initialization to zero bzero(s,20);
        clrscr();
        printf("Initail Success");
        getchar();
        return 0;
}

memset

原型:
extern void *memset(void *buffer, int c, int count);

用法:
#include <string.h>
功能:把buffer所指内存区域的前count个字节设置成字符c。
说明:返回指向buffer的指针。
举例:

// memset.c
#include <syslib.h>
#include <string.h>
int main()
{ 
   char *s="Golden Global View";
    clrscr();
   memset(s,'G',6);
   printf("%s",s);
   getchar();
    return 0;
}

setmem

原型:
extern void setmem(void *buf, unsigned int count, char ch);

用法:
#include <string.h>

功能:把buf所指内存区域前count个字节设置成字符ch。
说明:返回指向buf的指针。
举例:

// setmem.c
#include <syslib.h>
#include <string.h>
int main()
{ 
    char *s="Golden Global View";
    clrscr();
    setmem(s,6,'G');
    printf("%s",s);
    getchar();
    return 0;
}

综述:

bcopy和memcpy、bzero和memset、bcmp和memcmp的差别在哪里?
bcopy、bzero和bcmp是传统BSD的函数,属于POSIX标准;mem*是C90(以及C99)标准的C函数。区别在于,如果你打算把程序弄到一个符合C90/C99,但是不符合POSIX标准的平台时,后者比较有优势。

NetBSD的代码中有很多地方使用mem(他们更偏爱mem,以利于移植),即使内核也是如此,而FreeBSD的内核中则尽量避免使用(希望尽可能避免在内核中出现较多的C函数)。如果你提交代码的话需要注意这些约定。

在memset和bzero初始化数据间,我很多时候选择bzero, memset的一个缺点是第二个参数和第三个参数需要记忆,需要记住哪个是值和哪个是大小(如果不想查手册的话), 不可以弄错。

bzero()和memset()
今天用到一个字符数组初始化函数,bzero(),因为比较生疏,于是在查本地的一个C/C++函数库的时候并未见此函数,于是便觉得自己拥有的CHM版的库函数软件包有点山寨了,可是当自己调试程序的时候却发现此函数始终通不过编译。被逼上网查它们的区别,得答案如下:

C has memset(), the Berkeley UNIX C library has bzero(). They are not
identical, and bzero() pre dates memset() but is not widely available (since it's not part of standard C).


From CSDN:

【问】网上查了是
#include <string.h>
但是在vc6.0 和vs2005下还是报错,说bzero没有定义
【答】确实没有
可以自己定义一个
C/C++ code
#define bzero(a, b) memset(a, 0, b)
bzero()是在linux平台下用的,可以用memset()函数代替,这样就跨平台了。哈哈...
实在要用就像楼上说的那样。

转载自 http://blog.csdn.net/joeblackzqq/article/details/8257877

MySQL中函数CONCAT及GROUP_CONCAT

一、CONCAT()函数

CONCAT()函数用于将多个字符串连接成一个字符串。
使用数据表Info作为示例,其中SELECT id,name FROM info LIMIT 1;的返回结果为

+----+--------+
| id | name   |
+----+--------+
|  1 | BioCyc |
+----+--------+
1、语法及使用特点:

CONCAT(str1,str2,…)
返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。可以有一个或多个参数。

2、使用示例:

SELECT CONCAT(id, ‘,’, name) AS con FROM info LIMIT 1;返回结果为

+----------+
| con      |
+----------+
| 1,BioCyc |
+----------+

SELECT CONCAT(‘My’, NULL, ‘QL’);返回结果为

+--------------------------+
| CONCAT('My', NULL, 'QL') |
+--------------------------+
| NULL                     |
+--------------------------+
3、如何指定参数之间的分隔符

使用函数CONCAT_WS()。使用语法为:CONCAT_WS(separator,str1,str2,…)
CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符。分隔符的位置放在要连接的两个字符串之间。分隔符可以是一个字符串,也可以是其它参数。如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值。但是CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。

SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;返回结果为

+----------+
| con_ws   |
+----------+
| 1_BioCyc |
+----------+

SELECT CONCAT_WS(',','First name',NULL,'Last Name');返回结果为

+----------------------------------------------+
| CONCAT_WS(',','First name',NULL,'Last Name') |
+----------------------------------------------+
| First name,Last Name                         |
+----------------------------------------------+

二、GROUP_CONCAT()函数

GROUP_CONCAT函数返回一个字符串结果,该结果由分组中的值连接组合而成。
使用表info作为示例,其中语句SELECT locus,id,journal FROM info WHERE locus IN('AB086827','AF040764');的返回结果为

+----------+----+--------------------------+
| locus    | id | journal                  |
+----------+----+--------------------------+
| AB086827 |  1 | Unpublished              |
| AB086827 |  2 | Submitted (20-JUN-2002)  |
| AF040764 | 23 | Unpublished              |
| AF040764 | 24 | Submitted (31-DEC-1997)  |
+----------+----+--------------------------+
1、使用语法及特点:

`GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] [,col ...]]
[SEPARATOR str_val])`
在 MySQL 中,你可以得到表达式结合体的连结值。通过使用 DISTINCT 可以排除重复值。如果希望对结果中的值进行排序,可以使用 ORDER BY 子句。
SEPARATOR 是一个字符串值,它被用于插入到结果值中。缺省为一个逗号 (","),可以通过指定 SEPARATOR "" 完全地移除这个分隔符。
可以通过变量 group_concat_max_len 设置一个最大的长度。在运行时执行的句法如下: SET [SESSION | GLOBAL] group_concat_max_len = unsigned_integer;
如果最大长度被设置,结果值被剪切到这个最大长度。如果分组的字符过长,可以对系统参数进行设置:SET @@global.group_concat_max_len=40000;

2、使用示例:

语句 SELECT locus,GROUP_CONCAT(id) FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus; 的返回结果为

+----------+------------------+
| locus    | GROUP_CONCAT(id) |
+----------+------------------+
| AB086827 | 1,2              |
| AF040764 | 23,24            |
+----------+------------------+

语句 SELECT locus,GROUP_CONCAT(distinct id ORDER BY id DESC SEPARATOR '_') FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;的返回结果为

+----------+----------------------------------------------------------+
| locus    | GROUP_CONCAT(distinct id ORDER BY id DESC SEPARATOR '_') |
+----------+----------------------------------------------------------+
| AB086827 | 2_1                                                      |
| AF040764 | 24_23                                                    |
+----------+----------------------------------------------------------+

语句SELECT locus,GROUP_CONCAT(concat_ws(', ',id,journal) ORDER BY id DESC SEPARATOR '. ') FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;的返回结果为

+----------+--------------------------------------------------------------------------+
| locus    | GROUP_CONCAT(concat_ws(', ',id,journal) ORDER BY id DESC SEPARATOR '. ') |
+----------+--------------------------------------------------------------------------+
| AB086827 | 2, Submitted (20-JUN-2002). 1, Unpublished                               |
| AF040764 | 24, Submitted (31-DEC-1997) . 23, Unpublished                            |
+----------+--------------------------------------------------------------------------+

转载自
http://www.cnblogs.com/appleat/archive/2012/09/03/2669033.html