嵌入函数是借助像printf、scanf这类似的系库函数,   V2.00

File:      noifop.txt
Name:      优化分支代码——避免跳转指令堵塞流水线
Author:    zyl910
Blog:      http://blog.csdn.net/zyl910/
Version:   V2.00
Updata:    2006-10-11

一、函数

函数是一个涵盖完成得功能的举行代码段。我们可以拿函数看成一个”黑盒子”,
你要是以数据送进去就是能拿到结果, 而函数内部究竟是怎样行事的底,
外部程序是免精通之。外部程序所精晓的但是限于输入被函数什么以及函数输出什么。函数提供了编制程序的手法,使之易读、写、明白、排除错误、修改和护卫。 

计算1-n的和

#include "stdio.h"
void main()
{
    //write once only once 只写一次
    int i,s=0;

    for(i=1;i<=100;i++)
    {
        s+=i;
    }
    printf("%d \n",s);

    s=0;
    for(i=1;i<=80;i++)
    {
        s+=i;
    } 
    printf("%d \n",s);

    s=0;
    for(i=1;i<=555;i++)
    {
        s+=i;
    } 
    printf("%d \n",s);
}

 图片 1

背弃“write once only once 只写五次于”原则,重复。

#include "stdio.h"

/*
函数定义
int 表示函数的返回值(return),没返回值void
sum 表示函数的名称,符合变量命名
(int n) 参数列表,可以有0-n个
*/
int sum(int n)
{
    return n+1;
}

void main()
{
    int x=sum(100);
    x=sum(x);
    printf("%d \n",x);  //函数调用
    printf("%d \n",sum(9));
}

102

10

#include "stdio.h"

/*
函数定义
int 表示函数的返回值(return),没返回值void
sum 表示函数的名称,符合变量命名
(int n) 参数列表,可以有0-n个
*/
int sum(int n)
{
    int i,s=0;
    for(i=1;i<=n;i++)
    {
        s+=i;
    }
    return s;
}

void main()
{
    printf("%d \n",sum(100));
    printf("%d \n",sum(80));
    printf("%d \n",sum(555));
}

图片 2

 

(注意修改下充斥后的扩大名)

1.1、内置函数

内置函数是因像printf、scanf这好像的种类库函数,在编译的长河中,编译器会按照包含的条文件查找相应的仓库举办连接编译,假诺没有含头文件之言语,系统内部有很多库房文件,编译器就不可能找到相应之公文举办编译。内置函数有过多,可以参考《C语言标准库函数大全.chm》

一律、起因——饱和处理

1.2、自定义函数

  于编排图象处理程序时,常常出现RGB值超越[0,
255]限定之场所。这时,得做饱和拍卖,将越界数值饱和到分界,即这样的代码:
if (r <   0) r =   0;
if (r > 255) r = 255;
if (g <   0) g =   0;
if (g > 255) g = 255;
if (b <   0) b =   0;
if (b > 255) b = 255;

1.2.1、函数定义语法

       函数类型  函数名(类型 参数称,类型 参数名…)
      {
          函数体;
       }  

void show(char c)
{
    printf("%c\n",c);
}

  但诸如此类的代码执行效能是那些没有的。那是因if区片会编译成跳转指令,而跳转指令对于严重影响现代超流水线CPU的流水线功用。
  这时CPU产商提议了区区个缓解方案:一凡长了分段预测硬件,尽量减弱跳转指令对流程的影响;二凡是计划了MMX/SSE等SIMD指令集,它们纯天然就时有暴发饱和运算指令,而且还得并行总计。
  分支预测对于循环语句子所编译的跳转效果比好,因为当循环时必然会履行跳转,唯有最后一遍等巡回停止时才会面预测败北。而对做饱和处理效果固然多少好了,这是以老是循环总括出底RGB值都是休相同的(因为本就非是和一个像素),预测败北的可能性异常酷。所以分支预测对饱和处理做的孝敬微乎其微。
  而用SIMD指令也。这是平等种很健全的化解方案,在闹规则的事态下极力推荐SIMD指令。但由于高级语言无法描述SIMD指令,所以只好手工用汇编编辑SIMD代码,这如实为晓代码带来诸多不便。再者,大家偶尔要在比如Java、.Net这样的虚拟机平台下修图象处理程序,而这时候是无能为力直接运用SIMD指令集的。
  所以,大家要同种植在健康指令集下(能为此高档语言表述)做饱和处理的算法,且能规避if跳转。

1.2.2、函数调用

void main()
{
    show('a');
    show('b');
    show('c');
}

图片 3

 write once only once

#include "stdio.h"

//定义函数
//int 表示函数的返回值,无返回值void,return返回值
//sum 表示函数名称,与变量命名规则相同
//int n表示参数,可以有0-n个,形参
int sum(int n)
{
    int s=0,i;
    for(i=1;i<=n;i++)
    {
        s+=i;
    }
    return s;
}

void p(int n){
  printf("%d \t",n);
}

void l()
{
    printf("\n");
}

void main()
{
    p(sum(100));
    l();
    p(sum(300));
    l();
    p(sum(1000));
}

图片 4

  先尝试小于零时的饱和处理算法。

1.3、递归

函数直接或者直接的调用自己于递归。

int f(int n)
{
    if(n==1) return 1;
    return f(n-1)+n;
}

void main()
{
    printf("%d",f(100));
}

 

5050

f(n)=f(n-1)+n;

斐波这契数排(Fibonacci
sequence),又如黄金分割数列

//1 1 2 3 5 8 13 21 34
/* function
f(n)
f(5)=5
f(6)=8
f(7)=13
f(n)=f(n-1)+f(n-2)
f(7)=f(6)+f(5)
13=8+5
*/
int f(int n)  //n第几位
{
    if(n==1 || n==2) return 1;
    return f(n-1)+f(n-2);
}


void main()
{
    printf("%d",f(45));
}

  还记得“与”运算有啊功能也?当一个整数与掩码经行与运算时,全1掩码会保留原值,全0掩码会回来零。
  然后揣摩什么变化所急需的掩码。C语言相比较运算的的结果是0和1,咋样将它们变成全0或全1的掩码呢?答案非常粗略,求负
或 减一。由于求负写法简洁,所以爱好用要负:
n &= -(n >= 0)

二、指针

指南针是C语言中甚紧要之数据类型,了然指针有助于进一步深切精通C语言。

  首先用n与0举行相比较。当 n>=0 时,相比较结实为1;当 n<0
时,相比较结实为0。
  然后求负。当 n>=0 时,结果为-1(全1);当 n<0
时,结果也全0。
  再用原数与方求得的掩码举行“与”运算。

2.1、指针的概念

指南针是一个优异的变量,它其中储存的数值为演说成外存里的一个地点。 ”指针是一模一样栽保存变量地址之变量“,指针是一个特的变量

#include "stdio.h"
void main()
{
    int n=100;
    int *p=&n;

    printf("%d \n",p);  //输入逻辑地址
    printf("%p \n",p);  //%p 是以16进制的形式输出内存地址 
}

图片 5

  有了上边相当算法启发思维后,我们特别易想生处理>255状的算法:
n = (n | -(n >= 256) ) & 0xFF

2.2、指针的定义

int *p;
char *q;

“*”是一个表达符,用来验证这些变量是个指针变量,是匪可知简单的,但它不属变量名的平等有些
前方的路标识符表示指针变量所针对的变量的档次,而且不得不靠于这连串型的变量

  首先用n与256开展较。当 n<256 时,相比较结实吧0;当 n>=256
时,相比结实也1。
  然后求负。当 n>=256 时,结果为-1(全1);当 n<256
时,结果为0。
  再以原数与方求得的掩码举办“或”运算。
  最终跟0xFF举办和运算,使全1变成0xFF(十进制的255)。至于 n<256
的,本来就当[0, 255]限定外,结果未变换。

2.3、指针的初步化

// 定义int类型的变量a
int a = 10;

// 定义一个指针变量p
int *p;

// 将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
p = &a;

 

// 定义int类型的变量a
int a = 10;

// 定义一个指针变量p
// 并将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
int *p = &a;

 

  还可免得以还优化呢?由于一个累不容许还要小于0和过255,所以可以将方些许履代码合并成为一行。由于咱们一般是用结果保存及一个BYTE型变量中,举行相同次强制类型转换就推行了,不待“&
0xFF”。最终,每一趟写那么充足代码太累了,应该用她封装成宏:
#define LIMITSU_FAST(n, bits) ( (n) & -((n) >= 0) | -((n) >=
(1<<(bits))) )
#define LIMITSU_SAFE(n, bits) ( (LIMITSU_FAST(n, bits)) &
((1<<(bits)) – 1) )

2.4、指针运算符

char a = 10;
printf("修改前,a的值:%d\n", a);

// 指针变量p指向变量a
char *p = &a;

// 通过指针变量p间接修改变量a的值
*p = 9;

printf("修改后,a的值:%d", a);

图片 6

 

图片 7

取出指针所依靠于变量的价

char a = 10;

char *p;
p = &a;

char value = *p;
printf("取出a的值:%d", value);

演习:定义一个函数,使用指针完成几个数交换。

 

/* Note:Your choice is C IDE */
#include "stdio.h"
void main()
{
    int n=99;
    int *p=&n;  //定义指针p,可以指向int类型变量的地址
                //&n是取出n的地址

    printf("%d\n",p);
    printf("%p\n",p);

    printf("%d \n",*p);  //取出地址对应的值

    *p=55;  //将p指向的地址对应的值修改为55

    printf("%d\n",n);

    printf("%d \n",*p);  //取出地址对应的值

}

  bits代表限制多少个,如BYTE就是8。要是以为参数过多,可以还定义宏,或概念内联函数:
#define LIMITSU_BYTE(n) ((BYTE)(LIMITSU_FAST(n, 8)))

图片 8

  现在分析一下运算复杂度:
    if办法要2次比和2次跳转;
    本方用2次于(、2次将比结实转为数值)、2次求负、1糟同运算、1糟如故运算。

2.5、指针与数组

int array[10]={0,1,2,3,4,5,6,7,8,9},value; 

array就是频繁组的地方。

value=array[0];//也然而写成:value=*array; 
value=array[3];//也只是写成:value=*(array+3); 
value=array[4];//也不过写成:value=*(array+4); 

int array[10]; 
int (*ptr)[10];   //指向地点的地址
ptr=&array; 
直达例被ptr是一个指针,它的种是int (*)[10],他针对的路是int
[10] 
,大家为此全套数组的首地址来起初化它。在讲话ptr=&array中,array代表数组本 
身。 

int arrays[3]={1,2,3};
int (*ptr)[3];
ptr=&arrays;

printf(“%d\n”,**ptr+3);  //指向地点的地点

  可以看到,本办法在裁减2次跳转的景下,增加了共6蹩脚的各样运算。幸好位运算都是粗略指令,都是1独时钟周期内便会不辱使命的一声令下。所以当现世超流水线CPU情状下,这6次于位运算比if跳反开销少。

三、宏

C语言中提供的宏定义命令,其首要目标是啊程序员在编程时供一定的福利,并可以当得水准及加强程序的周转功用。

  下边琢磨的单独是诸如C语言这样“相比较运算结果是0或1”情形下之算法,如即使像BASIC这样“相比较运算结果是0或-1”怎么惩罚也?其实大简短,我们需要的正是0和-1,BASIC也支撑AND、OR、XOR等个运算符。代码就这样描写:
by =  ((n And (n >= 0) Or (n >= 256)) And &HFF)

3.1、简单宏定义

[#define指令(简单的大)]  #define 标识符替换列表

轮换列表是一模一样多样之C语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。当预处理器遭逢一个宏定义时,会开一个
“标识符”代表“替换列表”的笔录。在文书尾的始末中,不管标识符在其他职务现身,预处理器都碰面用替换列表代替它。

#define STE_LEN 80
#define TRUE 1
#define FALSE 0
#define PI 3.14159

  测试:略。请看old目录下之V100.rar

3.2、带参数的宏大

拉动参数的宏定义有如下格式:
[#define指令—带参数的极大]  #define 标识符(x1, x2,…,xn)替换列表

内x1,
x2,…,xn是标识符(宏的参数)。这么些参数可以以轮换列表中冲需要出现任意次。
当偌大的名字以及左括如泣如诉之间必须没有空格。假如有空格,预处理器会认为是于概念一个粗略的极大,其中(x1,x2,…,xn)是替换列表的一模一样组成部分。

比如,假定我们定义了如下的宏:

#define MAX(x,y)    ((x)>(y) ? (x) :(y))  
#define IS_EVEN(n)   ((n)%2==0)  

今昔只要后的程序中有如下语句:

i = MAX(j+k, m-n);  
if (IS_EVEN(i)) i++;  

先期处理器会将这个实践替换为

i = ((j+k)>(m-n)?(j+k):(m-n));  
if (((i)%2==0)) i++;  

200

#define PRINT_INT(x)    printf(“%d\n”, x)  

 

3.3、C语言中常用的宏大

01: 避免一个峰文件于还包含
#ifndef COMDEF_H
#define COMDEF_H
//头文件内容
#endif

02: 重新定义有品种
预防出于各样平台及编译器的例外,而暴发的系列字节数差别,方便移植。

typedef unsigned char boolean; /* Boolean value type. */
typedef unsigned long int uint32; /* Unsigned 32 bit value */
typedef unsigned short uint16; /* Unsigned 16 bit value */
typedef unsigned char uint8; /* Unsigned 8 bit value */
typedef signed long int int32; /* Signed 32 bit value */
typedef signed short int16; /* Signed 16 bit value */
typedef signed char int8; /* Signed 8 bit value */

//下面的匪指出接纳
typedef unsigned char byte; /* Unsigned 8 bit value type. */
typedef unsigned short word; /* Unsinged 16 bit value type. */
typedef unsigned long dword; /* Unsigned 32 bit value type. */
typedef unsigned char uint1; /* Unsigned 8 bit value type. */
typedef unsigned short uint2; /* Unsigned 16 bit value type. */
typedef unsigned long uint4; /* Unsigned 32 bit value type. */
typedef signed char int1; /* Signed 8 bit value type. */
typedef signed short int2; /* Signed 16 bit value type. */
typedef long int int4; /* Signed 32 bit value type. */
typedef signed long sint31; /* Signed 32 bit value */
typedef signed short sint15; /* Signed 16 bit value */
typedef signed char sint7; /* Signed 8 bit value */

03: 得到指定地址上之一个字节或字

#define MEM_B(x) (*((byte *)(x)))
#define MEM_W(x) (*((word *)(x)))

04: 求最特别价值与极致小值

#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
05: 拿到一个field在结构体(struct)中的偏移量

#define FPOS(type,field) ((dword)&((type *)0)->field)

06: 得到一个结构体中field所占有的字节数
#define FSIZ(type,field) sizeof(((type *)0)->field)
07: 根据LSB格式把有限独字节转化为一个Word

#define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])

08: 依照LSB格式把一个Word转化为简单只字节
#define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) &
0xFF)
09: 拿到一个变量的地址(word宽度)

#define B_PTR(var) ((byte *) (void *) &(var))
#define W_PTR(var) ((word *) (void *) &(var))

10: 拿到一个许的上位和低字节
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))

11: 重回一个比X大的最相近的8之翻番
#define RND8(x) ((((x) + 7)/8) * 8
12: 将一个假名转换为题写

#define UPCASE(c) (((c)>=’a’ && (c) <= ‘z’) ? ((c) – 0×20) :
(c))
13: 判断字符是匪是10上值的数字

#define DECCHK(c) ((c)>=’0′ && (c)<=’9′)
14: 判断字符是勿是16迈入值的数字

#define HEXCHK(c) (((c) >= ‘0’ && (c)<=’9′) ((c)>=’A’ &&
(c)<= ‘F’) \
((c)>=’a’ && (c)<=’f’))

15: 制止溢起底一个格局
#define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))

16: 重返数组元素的个数
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))

17: 重返一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by ) ((dword)(val) &
(dword)((mod_by)-1))

18: 对于IO空间映射在储存空间的构造,输入输出处理
#define inp(port) (*((volatile byte *)(port)))
#define inpw(port) (*((volatile word *)(port)))
#define inpdw(port) (*((volatile dword *)(port)))
#define outp(port,val) (*((volatile byte *)(port))=((byte)(val)))
#define outpw(port, val) (*((volatile word *)(port))=((word)(val)))
#define outpdw(port, val) (*((volatile dword
*)(port))=((dword)(val)))

19: 使用有宏跟踪调试
ANSI标准认证了两只预定义的宏名。它们是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__

C++中尚定义了 __cplusplus
假若编译器不是标准的,则可能一味协理上述宏名中之多少个,或向无协理。记住编译程序也许还提供任何预定义的宏名。
__LINE__ 及 __FILE__
宏指示,#line指令可以改变它的价值,总而言之话,编译时,它们包含程序的脚下行数和文件称。
__DATE__
宏指令含有形式呢月/日/年之错,表示来文件于翻译到代码时之日子。
__TIME__ 宏指令包含程序编译的时空。时间所以字符串表示,其形式为:
分:秒
__STDC__
宏指令的意义是编译时定义之。一般来讲,如若__STDC__现已定义,编译器将只接受不含其他不标准扩大的标准C/C++代码。如若实现是正式的,则大__STDC__包含十上前制常量1。假如她涵盖其他此外数,则贯彻是匪标准的。
__cplusplus
与规范c++一致的编译器把她定义也一个分包至少6为之数值。与业内c++不一致的编译器将动用有5各或更不见之数值。

可以定义宏,例如:当定义了_DEBUG,输出数据音讯与所在文件所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date)
printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif
20: 宏定义防止误选拔小括如泣如诉包含。
例如:
发出题目之概念:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp
+= nr;}
应下的定义: #difne DO(a,b) do{a+b;a++;}while(0)
例如:
if(addr)
DUMP_WRITE(addr,nr);
else
do_somethong_else();
//宏进行未来变成那样:
if(addr)
{memcpy(bufp,addr,nr); bufp += nr;};
else
do_something_else();

更多

二、推广

  该法不但适用做饱和拍卖,还好加大到外点去。

2.1 条件掩码

#define IFMASKNUM(n, c) ( (n) & -(c) )

参数:
  n: 掩码
  c: 条件。为0或1

再次回到值: 若c为1,则回回值是n;若c也0,则回值为0。

 

2.2 MIN与MAX

#define FASTMIN(a, b) ( (a) + ( ((b)-(a)) & -((b)<(a)) ) )
#define FASTMAX(a, b) ( (a) + ( ((b)-(a)) & -((b)>(a)) ) )

解释:
  将b与a举办相比时实际上会尽减法操作,而且前有“b-a”,这会吃编译器优化的。
  注意该方法会起溢起,所以唯有像C语言这样非会见丢来整数溢起大的语言才实施。

 

2.3 大小写转换

#define CHARUCASE(c) ( (c) ^ (0x20 & -((c)>=’a’ && (c)<=’z’) )
)
#define CHARLCASE(c) ( (c) ^ (0x20 & -((c)>=’A’ && (c)<=’Z’) ) )

解释:
‘A’的ASCII码是0x41
‘a’的ASCII码是0x61
她相差了0x20,就是D5不同。
故此我于准满意的时将该位取反就实施了(注意“^”是异或运算符)。

 

2.5 转为十六上前制字符

#define TOHEXCHAR(i) (‘0’ + (i) + ( (‘A’ – (‘0’+10)) & -((i)>=10) ))

解释:
中间的“( (‘A’ – (‘0’+10))”会编译优化成常数的。
有关哪些进展逆袭换,推测只有用查表法了。
 

相关文章