linux c 编程模板总结(一)

本文详细探讨了C语言中的fscanf, sscanf, fprintf, sprintf函数,包括它们的功能、用法和区别。重点介绍了如何在字符串处理中避免内存溢出和错误,并提供了若干实用的代码示例,如数字转换、字符串连接和格式化输出。文章还提到了内存管理和头文件的最佳实践,强调了初始化、内存安全和头文件简洁性的重要性。" 122311305,11590379,HTML5+CSS圣诞节日网页设计:7页带轮播效果,"['HTML', 'CSS', '前端开发', 'HTML5', '网页设计']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符处理函数:

size_t strlcpy (char *dst, const char *src, size_t dst_sz)

{
    size_t n;
    for (n = 0; n < dst_sz; n++)
     {
       if ((*dst++ = *src++) == '\0')
          break;
     }
    if (n < dst_sz)
    return n;
    if (n > 0)
    *(dst - 1) = '\0';
    return n + strlen (src);
}


size_t strlcat (char *dst, const char *src, size_t dst_sz)
{
    size_t len = strlen(dst);
    if (dst_sz < len)
    /* the total size of dst is less than the string it contains;
           this could be considered bad input, but we might as well
           handle it */
         return len + strlen(src);
    return len + strlcpy (dst + len, src, dst_sz - len);

}


用法:

                 memset(defaultTestUrl,0,255);//注意字符数组的初始化处理!

                bzero(buf,sizeof(buf));

                bzero(cmdRecv,sizeof(cmdRecv));
                strlcat(defaultTestUrl, "rtsp://", sizeof(defaultTestUrl));
                strlcpy(defaultServerIp,ips[playnum],sizeof(defaultServerIp));
                strlcat(defaultTestUrl, defaultServerIp, sizeof(defaultTestUrl));
                strlcat(defaultTestUrl, "/", sizeof(defaultTestUrl));
                strlcat(defaultTestUrl,videoNames[playnum], sizeof(defaultTestUrl));
                strlcat(defaultTestUrl,"\0", sizeof(defaultTestUrl));

                strlcpy(user->testurl,defaultTestUrl,sizeof(user->testurl));

               buffer[BUFFMAXSIZE-1]='\0'; //注意在字符串拷贝或链接组合时记得最后要给字符串数组的最后一位赋值'\0' !

               while (。。。){
                       。。。
                      buffer[i]=ch;
                      。。。
                 }

              buffer[BUFFMAXSIZE-1]='\0';

             write(cfd,buffer,strlen(buffer));


我们经常涉及到数字与字符串之间的转换,例如将32位无符号整数的ip地址转换为点分十进制的ip地址字符串,或者反过来。从给定的字符串中提取相关内容,例如给定一个地址:http://www.bokeyuan.cn:2345,我们要从地址中提出协议,主机地址和端口号。之前对字符串和数字之间的关系不是很熟悉,工作中经常涉及到这个,如是好好总结一下。C语言提供了一些列的格式化输入输出函数,最基本的是面向控制台标准输出和输入的printf和scanf,其实还有面向字符串的sprint和sscanf,面向文件的流的fprintf和fscanf。今天着重总结一下sprintf和sscanf系列函数,这两个函数类似于scanf和printf ,不同点是从字符串*buffer用于输入输出。

2、sprintf函数

  sprintf函数原型为 int sprintf(char *str, const char *format, ...)作用是格式化字符串,具体功能如下所示:

(1)将数字变量转换为字符串。

(2)得到整型变量的16进制和8进制字符串。

(3)连接多个字符串。

举例如下所示:

复制代码
 1     char str[256] = { 0 };
 2     int data = 1024;
 3     //将data转换为字符串
 4     sprintf(str,"%d",data);
 5     //获取data的十六进制
 6     sprintf(str,"0x%X",data);
 7     //获取data的八进制
 8     sprintf(str,"0%o",data);
 9     const char *s1 = "Hello";
10     const char *s2 = "World";
11     //连接字符串s1和s2
12     sprintf(str,"%s %s",s1,s2);
复制代码

3、sscanf函数

    sscanf函数原型为int sscanf(const char *str, const char *format, ...)将参数str的字符串根据参数format字符串来转换并格式化数据,转换后的结果存于对应的参数内。具体功能如下:

(1)根据格式从字符串中提取数据。如从字符串中取出整数、浮点数和字符串等。

(2)取指定长度的字符串

(3)取到指定字符为止的字符串

(4)取仅包含指定字符集的字符串

(5)取到指定字符集为止的字符串

sscanf可以支持格式字符%[]:

(1)-: 表示范围,如:%[1-9]表示只读取1-9这几个数字 %[a-z]表示只读取a-z小写字母,类似地 %[A-Z]只读取大写字母
(2)^: 表示不取,如:%[^1]表示读取除'1'以外的所有字符 %[^/]表示除/以外的所有字符
(3),: 范围可以用","相连接 如%[1-9,a-z]表示同时取1-9数字和a-z小写字母 
(4)原则:从第一个在指定范围内的数字开始读取,到第一个不在范围内的数字结束%s 可以看成%[] 的一个特例 %[^ ](注意^后面有一个空格!)

解析网址的例子如下所示:

复制代码
 1     const char *s = "http://www.baidu.com:1234";
 2     char protocol[32] = { 0 };
 3     char host[128] = { 0 };
 4     char port[8] = { 0 };
 5     sscanf(s,"%[^:]://%[^:]:%[1-9]",protocol,host,port);
 6 
 7     printf("protocol: %s\n",protocol);
 8     printf("host: %s\n",host);
 9     printf("port: %s\n",port);
10     
复制代码

4、snprintf函数

  snprintf函数是sprintf函数的更加安全版本,考虑到字符串的字节数,防止了字符串溢出。函数形式为:int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...);。最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。

int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...);

函数说明:最多从源串中拷贝n1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。

函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。

Result1(推荐的用法)

#include <stdio.h>
#include <stdlib.h>

int main()
{
     char str[10]={0,};
     snprintf(str, sizeof(str), "0123456789012345678");
     printf("str=%s/n", str);
     return 0;
}


       

sscanf:
sscanf() - 从一个字符串中读进与指定格式相符的数据.
函数原型:
Int sscanf( string str, string fmt, mixed var1, mixed var2 ... );
int scanf( const char *format [,argument]... );
说明:
sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。
其中的format可以是一个或多个 {%[*] [width] [{h | l | I64 | L}]type | ' ' | '\t' | '\n' | 非%符号}
注:
1、 * 亦可用于格式中, (即 %*d 和 %*s) 加了星号 (*) 表示跳过此数据不读入. (也就是不把此数据读入参数中)
2、{a|b|c}表示a,b,c中选一,[d],表示可以有d也可以没有d。
3、width表示读取宽度。
4、{h | l | I64 | L}:参数的size,通常h表示单字节size,I表示2字节 size,L表示4字节size(double例外),l64表示8字节size。
5、type :这就很多了,就是%s,%d之类。
6、特别的:%*[width] [{h | l | I64 | L}]type 表示满足该条件的被过滤掉,不会向目标参数中写入值
支持集合操作:
     %[a-z] 表示匹配a到z中任意字符,贪婪性(尽可能多的匹配)
     %[aB'] 匹配a、B、'中一员,贪婪性
     %[^a] 匹配非a的任意字符,贪婪性
例子:
1. 常见用法。
    char buf[512] = {0};
    sscanf("123456 ", "%s", buf);
printf("%s\n", buf);
结果为:123456
2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。
    sscanf("123456 ", "%4s", buf);
printf("%s\n", buf);
结果为:1234
3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。
    sscanf("123456 abcdedf", "%[^ ]", buf);
printf("%s\n", buf);
结果为:123456
4.  取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。
    sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);
printf("%s\n", buf);
结果为:123456abcdedf
5.  取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。
    sscanf("123456abcdedfBCDEF", "%[^A-Z]", buf);
printf("%s\n", buf);
结果为:123456abcdedf
6、给定一个字符串iios/12DDWDFF@122,获取 / 和 @ 之间的字符串,先将 "iios/"过滤掉,再将非'@'的一串内容送到buf中
sscanf("iios/12DDWDFF@122", "%*[^/]/%[^@]", buf);
printf("%s\n", buf);
结果为:12DDWDFF
7、给定一个字符串““hello, world”,仅保留world。(注意:“,”之后有一空格)
sscanf(“hello, world”, "%*s%s", buf);  
printf("%s\n", buf);
结果为:world
%*s表示第一个匹配到的%s被过滤掉,即hello被过滤了
如果没有空格则结果为NULL。

Sprintf:
sprintf 是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:
格式化字符串上。

fprintf():
  按格式输入到流,
其原型是int fprintf(FILE *stream, const char *format[, argument, ...]);其用法和printf()相同,不过不是写到控制台,而是写到流罢了
例:fprintf(fp,"%2d%s",4,"Hahaha");

fscanf():
  从流中按格式读取
,其原型是int fscanf(FILE *stream, const char *format[, address, ...]);其用法和scanf()相同,不过不是从控制台读取,而是从流读取罢了。
例:fscanf(fp,"%d%d" ,&x,&y);

fscanf,sscanf,fprintf,sprintf之间的区别是什么

f开头代表 file

s开头代表 string
可以这么理解.什么都不带的, 在标准 键盘 屏幕(控制台) 出入输出.




  1. if(1 == value)   //注意在作比价时要把常量放在前面,这样效率会好些!
  2.         return 1;  

              

  1.  char localMemory[MAX_SET_STR_LENGTH] = {0};  //注意在定义字符串指针等变量时记得进行初始化处理!
  2.  int* pData = NULL //注意在定义字符串指针等变量时记得进行初始化处理!

  1. #include <string.h>  
  2. #include <assert.h>  
  1. FruitShop* create_fruit_shop(int color)  
  2. {  
  3.     FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));  
  4.     assert(NULL != pFruitShop);  //注意时刻用assert函数来判断函数参数及数组指针是否为空,防止后面处理空指针,造成内存泄露!
  5.   
  6.     if(WHITE == color)    //注意在作比价时要把常量放在前面,这样效率会好些!
  7.     {  
  8.         pFruitShop->sell_apple = sell_white_apple;  
  9.         pFruitShop->sell_grape = sell_white_grape;  
  10.     }  
  11.     else  
  12.     {  
  13.         pFruitShop->sell_apple = sell_red_apple;  
  14.         pFruitShop->sell_grape = sell_red_grape;  
  15.     }  
  16.   
  17.     return pFruitShop;  
  18. }


继承性

[cpp]  view plain  copy
  1. typedef struct _parent  
  2. {  
  3.     int data_parent;  
  4.   
  5. }Parent;  
  6.   
  7. typedef struct _Child  
  8. {  
  9.     struct _parent parent;  
  10.     int data_child;  
  11.   
  12. }Child;  
    在设计C语言继承性的时候,我们需要做的就是把基础数据放在继承的结构的首位置即可。这样,不管是数据的访问、数据的强转、数据的访问都不会有什么问题。

  1. typedef struct _Action  
  2. {  
  3.     int type;  
  4.     struct _Action* next;  //结构体里面存放其他结构体
  5.   
  6.     void* pData;  
  7.     void (*process)(void* pData);  //结构体里面存放函数的方式
  8.   
  9. }Action; 

  1. typedef struct _Organizer  
  2. {  
  3.     int number;  
  4.     Action* pActionHead;  //结构体里面存放其他结构体
  5.   
  6.     Action* (*create)();  //结构体里面存放函数的方式
  7.     void (*restore)(struct _Organizer* pOrganizer);   //结构体里面存放函数的方式
  8. }Organizer; 

  1. void restore(struct _Organizer* pOrganizer)  
  2. {  
  3.     Action* pHead;  
  4.     assert(NULL != pOrganizer);  
  5.   
  6.     pHead = pOrganizer->pActionHead;  
  7.     pHead->process(pHead->pData);  
  8.     pOrganizer->pActionHead = pHead->next;  
  9.     pOrganizer->number --;  
  10.     free(pHead);  
  11.     return;  
  1. typedef struct _AssemblePersonalComputer  
  2. {  
  3.     void (*assemble_cpu)();  
  4.     void (*assemble_memory)();  
  5.     void (*assemble_harddisk)();  
  6.   
  7. }AssemblePersonalComputer;  
    对于一个希望配置intel cpu,samsung 内存、日立硬盘的朋友。他可以这么设计,

[cpp]  view plain  copy
  1. void assemble_intel_cpu()  
  2. {  
  3.     printf("intel cpu!\n");  
  4. }  
  5.   
  6. void assemble_samsung_memory()  
  7. {  
  8.     printf("samsung memory!\n");  
  9. }  
  10.   
  11. void assemble_hitachi_harddisk()  
  12. {  
  13.     printf("hitachi harddisk!\n");  
  14. }  
    而对于一个希望配置AMD cpu, kingston内存、西部数据硬盘的朋友。他又该怎么做呢?

[cpp]  view plain  copy
  1. void assemble_amd_cpu()  
  2. {  
  3.     printf("amd cpu!\n");  
  4. }  
  5.   
  6. void assemble_kingston_memory()  
  7. {  
  8.     printf("kingston memory!\n");  
  9. }  
  10.   
  11. void assmeble_western_digital_harddisk()  
  12. {  
  13.     printf("western digital harddisk!\n");  

 假设有两个水果店都在卖水果,都卖苹果和葡萄。其中一个水果店买白苹果和白葡萄,另外一个水果店卖红苹果和红葡萄。所以说,对于水果店而言,尽管都在卖水果,但是两个店卖的品种不一样。  

    既然水果不一样,那我们先定义水果。

[cpp]  view plain  copy
  1. typedef struct _Apple  
  2. {  
  3.     void (*print_apple)();  
  4. }Apple;  
  5.   
  6. typedef struct _Grape  
  7. {  
  8.     void (*print_grape)();  
  9. }Grape;   
    上面分别对苹果和葡萄进行了抽象,当然它们的具体函数也是不一样的。

[cpp]  view plain  copy
  1. void print_white_apple()  
  2. {  
  3.     printf("white apple!\n");  
  4. }  
  5.   
  6. void print_red_apple()  
  7. {  
  8.     printf("red apple!\n");  
  9. }  
  10.   
  11. void print_white_grape()  
  12. {  
  13.     printf("white grape!\n");  
  14. }  
  15.   
  16. void print_red_grape()  
  17. {  
  18.     printf("red grape!\n");  
  19. }  
    完成了水果函数的定义。下面就该定义工厂了,和水果一样,我们也需要对工厂进行抽象处理。

[cpp]  view plain  copy
  1. typedef struct _FruitShop  
  2. {  
  3.     Apple* (*sell_apple)();  
  4.     Apple* (*sell_grape)();  
  5. }FruitShop;  
    所以,对于卖白苹果、白葡萄的水果店就该这样设计了,红苹果、红葡萄的水果店亦是如此。

[cpp]  view plain  copy
  1. Apple* sell_white_apple()  
  2. {  
  3.     Apple* pApple = (Apple*) malloc(sizeof(Apple));  
  4.     assert(NULL != pApple);  
  5.   
  6.     pApple->print_apple = print_white_apple;  
  7.     return pApple;  
  8. }  
  9.   
  10. Grape* sell_white_grape()  
  11. {  
  12.     Grape* pGrape = (Grape*) malloc(sizeof(Grape));  
  13.     assert(NULL != pGrape);  
  14.   
  15.     pGrape->print_grape = print_white_grape;  
  16.     return pGrape;  
  17. }   
    这样,基本的框架就算搭建完成的,以后创建工厂的时候,

[cpp]  view plain  copy
  1. FruitShop* create_fruit_shop(int color)  
  2. {  
  3.     FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));  
  4.     assert(NULL != pFruitShop);  
  5.   
  6.     if(WHITE == color)  
  7.     {  
  8.         pFruitShop->sell_apple = sell_white_apple;  
  9.         pFruitShop->sell_grape = sell_white_grape;  
  10.     }  
  11.     else  
  12.     {  
  13.         pFruitShop->sell_apple = sell_red_apple;  
  14.         pFruitShop->sell_grape = sell_red_grape;  
  15.     }  
  16.   
  17.     return pFruitShop;  
  18. }  

下面我们主要从两个方面对头文件进行分析,即头文件是做什么的,头文件编写的过程中要注意些什么?

    (1)头文件的作用

    其实很多的编程语言是没有头文件的,比如说C#、java语言。为什么呢,因为这些语言数据结构和函数操作是捆绑在一起的。而C语言则不一样,它是把头文件和实现文件分开来的。头文件的内容主要有哪些呢,也就是嵌套头文件、宏定义、数据类型、函数原型定义、static函数等等。

    (2)头文件的编写

    a)头文件要简洁

    很多源文件在编写的时候常常喜欢添加很多的头文件,不管是需要的还是不需要的。可是,我们要知道,头文件的作用主要是定义数据类型和函数类型的。本质上来说,头文件很少会创建实质性的代码,不管是数据段的内容,还是代码段的内容。简洁的头文件不仅有利于快速排除编译故障,还能提高编译的速度。有经验的朋友都知道,源文件的编译错误比较容易解决,而头文件的编译错误常常十分复杂。所以,我们必须在一切可能的条件下保证头文件的简洁。

    b)头文件注意互斥性

   注意头文件的互斥性,需要我们在开发中养成良好的编程习惯。不管是创建头文件,首先要做的事情就是添加编译宏。看上去这是一个十分不起眼的举动,但是常常可以帮助你减少许多不必要的麻烦。

  1. #ifndef _DATA_H  
  2. #define _DATA_H  
  3.   
  4. #endif  

    c)全局变量不要在头文件里面定义,如果是外部引用,必须添加上extern

  1. extern int g_Data;    

    d)不要在头文件里面实现函数,如果要实现,也必须要添加static

[cpp] view plain copy
  1. static int add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

    e)头文件当中如果需要嵌入别的头文件,那么只是为了引用另外一个头文件的数据结构

    f)头文件中引用的数据类型如果没有说明,那么在被源文件引用的时候,只要保证其他的头文件存在这个数据类型定义即可

    g)源文件引用头文件的时候需要注意头文件的顺序,有的时候顺序变了,可能编译就失败了。原因就是之前后面头文件中定义的数据类型找不到出处了

    h)某些工程没有把头文件和源文件绑定在一起,修改头文件必须删除工程重新编译

    i)头文件的存在只是为了源文件才存在的,如果没有必要不要写头文件。要写,影响范围也要控制在最小的范围内

    j)如果头文件定义了数据结构,那么需要嵌入引用头文件,反之如果只是指针,声明一下即可,比如说

  1. struct _Data;  
  2. typedef struct _Data Data;  

   k)如果有可能经常整理自己的头文件,全部删除再一个一个添加,这样就知道哪些是我们需要的,哪些不是

    l)对于某些宏,如果不确定文件本身是在哪里定义的,可以在源文件中再定义一次,这样编译器就会精确提示我们原来这个宏是在那里定义的

    好了,差不多就这么多了。


头文件 *.h文件例子:

  1. #ifndef _DATA_H  
  2. #define _DATA_H  
  3.   
  4. #endif  
源文件*.c文件例子:

  1. #include <pthread.h>  
  2. #include <semaphore.h>  
  3. #include <stdio.h>  
  4. #include <stdlib.h>  
  5. // Set either of these to 1 to prevent CPU reordering  
  6. #define USE_CPU_FENCE              0  
  7. #define USE_SINGLE_HW_THREAD       0  // Supported on Linux, but not Cygwin or PS3  
  8.   
  9. #if USE_SINGLE_HW_THREAD       ---------在头文件中使用if ... endif
  10. #include <sched.h>  
  11. #endif  
  12.   
  13. //-------------------------------------  
  14. //  MersenneTwister  
  15. //  A thread-safe random number generator with good randomness  
  16. //  in a small number of instructions. We'll use it to introduce  
  17. //  random timing delays.  
  18. //-------------------------------------  
  19. #define MT_IA  397  
  20. #define MT_LEN 624  
  21.   
  22. class MersenneTwister  
  23. {  
  24.     unsigned int m_buffer[MT_LEN];  
  25.     int m_index;  
  26.   
  27. public:  
  28.     MersenneTwister(unsigned int seed);  
  29.     // Declare noinline so that the function call acts as a compiler barrier:  
  30.     unsigned int integer() __attribute__((noinline));  
  31. };  
  32.   
  33. MersenneTwister::MersenneTwister(unsigned int seed)  
  34. {  
  35.     // Initialize by filling with the seed, then iterating  
  36.     // the algorithm a bunch of times to shuffle things up.  
  37.     for (int i = 0; i < MT_LEN; i++)  
  38.         m_buffer[i] = seed;  
  39.     m_index = 0;  
  40.     for (int i = 0; i < MT_LEN * 100; i++)  
  41.         integer();  
  42. }  
  43.   
  44. unsigned int MersenneTwister::integer()  
  45. {  
  46.     // Indices  
  47.     int i = m_index;  
  48.     int i2 = m_index + 1; if (i2 >= MT_LEN) i2 = 0; // wrap-around  
  49.     int j = m_index + MT_IA; if (j >= MT_LEN) j -= MT_LEN; // wrap-around  
  50.   
  51.     // Twist  
  52.     unsigned int s = (m_buffer[i] & 0x80000000) | (m_buffer[i2] & 0x7fffffff);  
  53.     unsigned int r = m_buffer[j] ^ (s >> 1) ^ ((s & 1) * 0x9908B0DF);  
  54.     m_buffer[m_index] = r;  
  55.     m_index = i2;  
  56.   
  57.     // Swizzle  
  58.     r ^= (r >> 11);  
  59.     r ^= (r << 7) & 0x9d2c5680UL;  
  60.     r ^= (r << 15) & 0xefc60000UL;  
  61.     r ^= (r >> 18);  
  62.     return r;  
  63. }  
  64. //-------------------------------------  
  65. //  Main program, as decribed in the post  
  66. //-------------------------------------  
  67. sem_t beginSema1;  
  68. sem_t beginSema2;  
  69. sem_t endSema;  
  70.   
  71. int X, Y;  
  72. int r1, r2;  
  73.   
  74. /* thread1Func, thread2Func for StoreLoad */  
  75. /* thread3Func, thread4Func for StoreStore LoadLoad */  
  76. /* thread5Func, thread6Func for LoadStore */  
  77.   
  78. void *thread1Func(void *param)  
  79. {  
  80.     MersenneTwister random(1);  
  81.     for (;;)  
  82.     {  
  83.         sem_wait(&beginSema1);  // Wait for signal  
  84.         while (random.integer() % 8 != 0) {}  // Random delay  
  85.   
  86.         // ----- THE TRANSACTION! -----  
  87.         X = 1;  
  88. #if USE_CPU_FENCE                    ---------在子函数中使用if ... endif
  89.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  90. #else  
  91.         asm volatile("" ::: "memory");  // Prevent compiler reordering  
  92. #endif  
  93.         r1 = Y;  
  94.   
  95.         sem_post(&endSema);  // Notify transaction complete  
  96.     }  
  97.     return NULL;  // Never returns  
  98. };  
  99.   
  100. void *thread2Func(void *param)  
  101. {  
  102.     MersenneTwister random(2);  
  103.     for (;;)  
  104.     {  
  105.         sem_wait(&beginSema2);  // Wait for signal  
  106.         while (random.integer() % 8 != 0) {}  // Random delay  
  107.         // ----- THE TRANSACTION! -----  
  108.         Y = 1;  
  109. #if USE_CPU_FENCE  
  110.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  111. #else  
  112.         asm volatile("" ::: "memory");  // Prevent compiler reordering  
  113. #endif  
  114.         r2 = X;  
  115.   
  116.         sem_post(&endSema);  // Notify transaction complete  
  117.     }  
  118.     return NULL;  // Never returns  
  119. };  
  120.   
  121. void *thread3Func(void *param)  
  122. {  
  123.     MersenneTwister random(1);  
  124.     for (;;)  
  125.     {  
  126.         sem_wait(&beginSema1);  // Wait for signal  
  127.         while (random.integer() % 8 != 0) {}  // Random delay  
  128.   
  129.         // ----- THE TRANSACTION! -----  
  130.         X = 1;  
  131. #if USE_CPU_FENCE  
  132.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  133. #else  
  134.         asm volatile("" ::: "memory");  // Prevent compiler reordering only  
  135. #endif  
  136.         Y = 1;  
  137.         sem_post(&endSema);  // Notify transaction complete  
  138.     }  
  139.     return NULL;  // Never returns  
  140. };  
  141.   
  142. void *thread4Func(void *param)  
  143. {  
  144.     MersenneTwister random(2);  
  145.     for (;;)  
  146.     {  
  147.         sem_wait(&beginSema2);  // Wait for signal  
  148.         while (random.integer() % 8 != 0) {}  // Random delay  
  149.         // ----- THE TRANSACTION! -----  
  150.         r1 = Y;  
  151. #if USE_CPU_FENCE  
  152.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  153. #else  
  154.         asm volatile("" ::: "memory");  // Prevent compiler reordering only  
  155. #endif  
  156.         r2 = X;  
  157.         sem_post(&endSema); // Notify transaction complete  
  158.     }  
  159.     return NULL;  // Never returns  
  160. };  
  161. void *thread5Func(void *param)  
  162. {  
  163.     MersenneTwister random(1);  
  164.     for (;;)  
  165.     {  
  166.         sem_wait(&beginSema1);  // Wait for signal  
  167.         while (random.integer() % 8 != 0) {}  // Random delay  
  168.         // ----- THE TRANSACTION! -----  
  169.         r1 = X;  
  170. #if USE_CPU_FENCE  
  171.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  172. #else  
  173.         asm volatile("" ::: "memory");  // Prevent compiler reordering only  
  174. #endif  
  175.         Y = 1;  
  176.         sem_post(&endSema);  // Notify transaction complete  
  177.     }  
  178.     return NULL;  // Never returns  
  179. };  
  180. void *thread6Func(void *param)  
  181. {  
  182.     MersenneTwister random(2);  
  183.     for (;;)  
  184.     {  
  185.         sem_wait(&beginSema2);  // Wait for signal  
  186.         while (random.integer() % 8 != 0) {}  // Random delay  
  187.         // ----- THE TRANSACTION! -----  
  188.         r2 = Y;  
  189. #if USE_CPU_FENCE  
  190.         asm volatile("mfence" ::: "memory");  // Prevent CPU reordering  
  191. #else  
  192.         asm volatile("" ::: "memory");  // Prevent compiler reordering only  
  193. #endif  
  194.         X = 1;  
  195.         sem_post(&endSema);  // Notify transaction complete  
  196.     }  
  197.     return NULL;  // Never returns  
  198. };  
  199. int main(int argc, char** argv)  
  200. {  
  201.     // Check Argument  
  202.     if (argc > 2)  
  203.     {  
  204.         printf("Too Many Arguments: Only Need One.\n");  
  205.         return 0;  
  206.     }  
  207.     if (argc == 1)  
  208.     {  
  209.         printf("You Should Give an Argument: 1 or 2 or 3.\n");  
  210.         return 0;  
  211.     }  
  212.     int i;  
  213.     i = atoi(argv[1]);  
  214.     if (i < 1 || i > 3)  
  215.     {  
  216.         printf("Wrong Argument: Only 1 or 2 or 3 Can be Input.\n");  
  217.         return 0;  
  218.     }  
  219.     // Initialize the semaphores  
  220.     sem_init(&beginSema1, 0, 0);  
  221.     sem_init(&beginSema2, 0, 0);  
  222.     sem_init(&endSema, 0, 0);  
  223.     // Spawn the threads  
  224.     pthread_t thread1, thread2;  
  225.     // 1. StoreLoad Reorder Tests  
  226.     if (i == 1)   
  227.     {  
  228.         pthread_create(&thread1, NULL, thread1Func, NULL);  
  229.         pthread_create(&thread2, NULL, thread2Func, NULL);  
  230.     }  
  231.     // 2. LoadLoad && StoreStore Reorder Tests  
  232.     else if (i == 2)  
  233.     {  
  234.         pthread_create(&thread1, NULL, thread3Func, NULL);  
  235.         pthread_create(&thread2, NULL, thread4Func, NULL);  
  236.     }  
  237.     // 3. LoadStore Reorder Tests  
  238.     else  
  239.     {  
  240.         pthread_create(&thread1, NULL, thread5Func, NULL);  
  241.         pthread_create(&thread2, NULL, thread6Func, NULL);  
  242.     }  
  243.   
  244. #if USE_SINGLE_HW_THREAD               ---------在main函数中调用if ... endif
  245.     // Force thread affinities to the same cpu core.  
  246.     cpu_set_t cpus;  
  247.     CPU_ZERO(&cpus);  
  248.     CPU_SET(0, &cpus);  
  249.     pthread_setaffinity_np(thread1, sizeof(cpu_set_t), &cpus);  
  250.     pthread_setaffinity_np(thread2, sizeof(cpu_set_t), &cpus);  
  251. #endif  
  252.     // Repeat the experiment ad infinitum  
  253.     int detected = 0;  
  254.     for (int iterations = 1; ; iterations++)  
  255.     {  
  256.         // Reset X and Y  
  257.         X = 0;  
  258.         Y = 0;  
  259.         // Signal both threads  
  260.         sem_post(&beginSema1);  
  261.         sem_post(&beginSema2);  
  262.         // Wait for both threads  
  263.         sem_wait(&endSema);  
  264.         sem_wait(&endSema);  
  265.         // Check if there was a simultaneous reorder  
  266.             // 1. StoreLoad Reorder  
  267.         if (i == 1)  
  268.         {  
  269.             if (r1 == 0 && r2 == 0)  
  270.             {  
  271.                 detected++;  
  272.                 printf("%d reorders detected after %d iterations\n", detected, iterations);  
  273.             }  
  274.         }  
  275.         // 2. LoadLoad && StoreStore Reorder  
  276.         else if (i == 2)  
  277.         {  
  278.             if (r1 == 1 && r2 == 0)  
  279.             {  
  280.                 detected++;  
  281.                 printf("%d reorders detected after %d iterations\n", detected, iterations);  
  282.             }  
  283.         }  
  284.         // 3. LoadStore Reorder  
  285.         else   
  286.         {  
  287.             if (r1 == 1 && r2 == 1)  
  288.             {  
  289.                 detected++;  
  290.                 printf("%d reorders detected after %d iterations\n", detected, iterations);  
  291.             }  
  292.         }  
  293.     }  
  294.     return 0;  // Never returns  
  295. }  



尽量减少代码嵌套
我们平时写if,switch或for语句是常有的事儿,也一定写过多层if或for语句嵌套的情况,如果代码里的嵌套超过3层,阅读起来就会非常困难了。我们应该尽量避免代码嵌套多层,最好不要超过2层。下面我来说说我平时一些减少嵌套的技巧或方法。
if语句嵌套的问题
多层if语句嵌套是常有的事情,有什么好的方法可以减少嵌套呢?
1、 尽早终止函数或返回数据
如果符合某个条件下可以直接终止函数,则应该将这个条件放在第一位。我们来看看下面的例子。
1 if(condition1) { 2 if(condition2){ 3 if(condition3){ 4 } 5 else{ 6 return; 7 } 8 } 9 else{10 return;11 } 12 }13 else {14 return;15 }
这段代码中if语句嵌套了3层,看起来已经很复杂了,我们可以将最后面的return提取到最前面去。
1 if(!condition1){ 2 return; 3 } 4 if(!condition2){ 5 return; 6 } 7 if(!condition3){ 8 return; 9 }10 //doSth
这段代码中,我们把condition1等于false的语句提取到前面,直接终止函数,将多层嵌套的if语句重构成只有一层if语句,代码也更清晰了。
注意:一般情况下,我们写if语句会将条件为true的情况写在前面,这也比较符合我们的思维习惯。如果是多层嵌套的情况,应该优先减少if语句的嵌套
2、不适用if语句或switch语句
条件语句一般来说是不可避免的,有的时候,我们要判断很多条件就会写很多if-elseif语句,嵌套的话,就更加麻烦了。如果有一天增加了新需求,我们就要去增加一个if分支语句,这样不仅修改起来麻烦,而且容易出错。《代码大全》提出的表驱动法可以有效地解决if语句带来的问题。我们来看下面这个例子:
1 if(condition == “case1”){ 2 return 1; 3 } 4 elseif(condition == “case2”){ 5 return 2; 6 } 7 elseif(condition == “case3”){ 8 return 3; 9 }10 elseif(condition == “case4”){11 return 4;12 }
这段代码分别依次判断了四种情况,如果再增加一种情况,我们就要再新增一个if分支,这样就可能造成潜在的问题,如何去优化这段代码呢?我们可以采用一个Map或Dictionary来将每一种情况和相应值一一对应。
1 var map = {2 "case1":1,3 "case2":2,4 "case3":3,5 "case4":46 }7 return map[condition];
通过map优化后,整个代码不仅更加简洁,修改起来也更方便而且不易出错了。
当然,很多时候我们的条件判断语句并不是这么简单的,可能会涉及到复杂的逻辑运算,大家可以查看《代码大全》第18章,其中有详细的介绍。
3、提取内层嵌套为一个函数进行调用
多层嵌套的时候,我们还可以将内层嵌套提取到一个新的函数中,然后调用该函数,这样代码也就更清晰了。
for循环嵌套优化
for循环嵌套相比于if嵌套来说更加复杂,阅读起来会更麻烦,下面说说几点要注意的东西:
1、最多只能两层for循环嵌套
2、 提取内层循环到新函数中
3、多层循环时,不要简单地位索引变量命名为i,j,k等,容易造成混淆,要有具体的意思

嵌套For循环性能优化案例

某日,在JavaEye上看到一道面试题,题目是这样的:请对以下的代码进行优化 
Java代码   收藏代码
  1. for (int i = 0; i < 1000; i++)  
  2.     for (int j = 0; j < 100; j++)  
  3.         for (int k = 0; k < 10; k++)  
  4.             testFunction (i, j, k);  
  1. int i, j, k;  
  2. for (i = 0; i < 10; i++)  
  3.     for (j = 0; j < 100; j++)  
  4.         for (k = 0; k < 1000; k++)  
  5.             testFunction (k, j, i);  

该方案在方案一的基础上,将循环变量的实例化放到循环外,这样可以进一步减少相关循环变量的实例化次数  

在嵌套For循环中,将循环次数多的循环放在内侧,循环次数少的循环放在外侧,其性能会提高;减少循环变量的实例化,其性能也会提高。

应用:不借助第三个数交换两数,有趣应用4

  1. void Swap(int &a, int &b)  
  2. {  
  3.     if (a != b)  
  4.     {  
  5.         a ^= b;  
  6.         b ^= a;  
  7.         a ^= b;  
  8.     }  
乘除法运算要用移位的方式,效率更高---->>,<<


前言:

我们在写程序的时候,总是或多或少会加入一些printf之类的语句用于输出调试信息,但是printf语句有个很不方便的地方就是当我们需要发布程序的时候要一条一条的把这些语句删除,而一旦需要再次调试的时候,这些语句又不得不一条条的加上,这给我们带来了很大的不便,浪费了我们很多的时间,也造成了调试的效率低下。所以,很多人会选择使用宏定义的方式来输出调试语句。

比如,定义一个宏开关:

#define __DEBUG

当需要调试的时候,使用语句:

#ifdef __DEBUG

printf(xxx);

#endif

这种方式的调试,可以通过undef __DEBUG的方式让告知编译器不编译这些语句,从而不再输出这些语句。但是这种方式的麻烦之处也是显而易见的,每一条调试语句都需要使用两条宏定义来包围,这不但在代码的编写上不便,源码结构也不好看,工作量依然不小。

如果我们能够把这三条语句编程一条,那该多舒服呀~,于是,我们想到使用这样的语句:

#ifdef __DEBUG

#define DEBUG(info)    printf(info)

#else

#define DEBUG(info)

#endif

这样,我们在编写代码的时候,使用DEBUG一条语句就可以了,我们把宏开关__DEBUG打开,所有的DEBUG(info)宏定义信息都会被替换为printf(info),关上则会被替换成空,因此不会被编译。嗯,这次方便多了,一条语句就可以了~~~ 但是,问题也随之而来了,printf是支持多个参数的,而且是不定参数,当你使用下面这样的语句时就会报错:

DEBUG("%s",msg)

这是因为,DEBUG(info)这条宏定义只支持一个参数的替换。

因此,我们希望DEBUG能够像printf那样,支持多个参数,并且这些参数刚好展开成为printf语句本身使用的参数,譬如:我们希望DEBUG("%s",msg)能够展开为printf("%s",msg)

正文:

通过网上的资料查阅,发现自C99规范以后,编译器就开始支持不定参数的宏定义,就像printf一样。

大家可以看看这篇文章:http://blog.youkuaiyun.com/aobai219/archive/2010/12/22/6092292.aspx

于是,我们定义了一个这样的东东:

#define DEBUG(format, ...) printf (format, ##__VA_ARGS__)(' ## '的意思是,如果可变参数被忽略或为空,将使预处理器( preprocessor )去除掉它前面的那个逗号。)

于是乎,我们神奇地发现,DEBUG完全取代了printf,所有的DEBUG(…)都被完成的替换成了printf(…),再也不会因那个可恶的逗号而烦恼了。

但是,我们发现,光有printf还不够,虽然调试信息是输出了,可是很多的调试信息输出,我们并不能一下子知道这条信息到底是在那里打印出来的,于是,我们又想,能不能把当前所在文件名和源码行位置也打印出来呢,这样不就一目了然了吗,哪里还用的着去想,去找调试信息在哪里输出的呢,都已经打印出来了!

于是我们就有了下面的故事。。。

编译器内置宏:

先介绍几个编译器内置的宏定义,这些宏定义不仅可以帮助我们完成跨平台的源码编写,灵活使用也可以巧妙地帮我们输出非常有用的调试信息。

ANSI C标准中有几个标准预定义宏(也是常用的):

__LINE__:在源代码中插入当前源代码行号;

__FILE__:在源文件中插入当前源文件名;

__DATE__:在源文件中插入当前的编译日期

__TIME__:在源文件中插入当前编译时间;

__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;

__cplusplus:当编写C++程序时该标识符被定义。

编译器在进行源码编译的时候,会自动将这些宏替换为相应内容。

看到这里,你的眼睛应该一亮了吧,嗯,是的,__FILE__和__LINE__正是我们前面想要的输出的,于是,我们的每一条语句都变成了:

DEBUG("FILE: %s, LINE: %d…",__FILE__,__LINE__,…)

其实没有必要,__FILE__本身就会被编译器置换为字符常量,于是乎我们的语句又变成了这样:

DEBUG("FILE:"__FILE__", LINE: %d…",__LINE__,…)

但是,我们还是不满足,依然发现,还是很讨厌,为什么每条语句都要写"FILE:"__FILE__", LINE: %d 以及,__LINE,这两个部分呢?这不是浪费我们时间么?

哈哈,是的,这就是本次大结局,把DEBUG写成这样:

DEBUG(format,...) printf("FILE: "__FILE__", LINE: %d: "format"/n", __LINE__, ##__VA_ARGS__)

没错,就是这样!下面,所有的DEBUG信息都会按照这样的方式输出:

FILE: xxx, LINE: xxx, …….

最后:

最后,老规矩,coding测试。

  1. //============================================================================  
  2. // Name : debug.cpp  
  3. // Author : boyce  
  4. // Version : 1.0  
  5. // Copyright : pku  
  6. // Description : Hello World in C++, Ansi-style  
  7. //============================================================================  
  8. #include  
  9. #define __DEBUG__  
  10. #ifdef __DEBUG__  
  11. #define DEBUG(format,...) printf("File: "__FILE__", Line: %05d: "format"/n", __LINE__, ##__VA_ARGS__)  
  12. #else  
  13. #define DEBUG(format,...)  
  14. #endif  
  15. int main() {  
  16.     char str[]="Hello World";  
  17.     DEBUG("A ha, check me: %s",str);  
  18.     return 0;  
  19. }  

测试结果:

是不是感觉很酷?O(∩_∩)O哈哈~

事实上,一般来说malloc的数据是不需要我们监督的,因为内存分配的时候,通常库函数会比我们要求的size多分配几个字节,这样在free的时候就可以判断内存的开头和结尾处有没有指针溢出。朋友们可以试一下下面这段代码。

[cpp]  view plain  copy
  1. void heap_memory_leak()  
  2. {  
  3.     char* pMem = (char*)malloc(100);  
  4.     pMem[-1] = 100;  
  5.     pMem[100] = 100;  
  6.     free(pMem);  
  7. }  
    pMem[-1] = 100是堆左溢出, pMem[100]是堆右溢出




1.获得int型最大值

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.         return  (1<<31) - 1; //2147483647, 由于优先级关系,括号不可省略   
  3. }  

另一种写法

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  -(1<<-1) - 1; //2147483647   
  3. }  

另一种写法

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  ~(1<<31); //2147483647   
  3. }  

C语言中不知道int占几个字节时候

[java]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  ((unsigned  int )- 1 ) >>  1 ; //2147483647   
  3. }  

2.获得int型最小值

[cpp]  view plain  copy
  1. int  getMinInt(){  
  2.     return  1<<31; //-2147483648   
  3.  }  

另一种写法

[cpp]  view plain  copy
  1. int  getMinInt(){  
  2.     return  1 << -1; //-2147483648   
  3. }  

3.获得long类型的最大值


C语言版

[cpp]  view plain  copy
  1. long  getMaxLong(){  
  2.     return  ((unsigned  long )-1) >> 1; //2147483647   
  3. }  

JAVA版

[java]  view plain  copy
  1. long  getMaxLong(){  
  2.     return  (( long ) 1 << 127 )- 1 ; //9223372036854775807   
  3. }  
获得long最小值,和其他类型的最大值,最小值同理.

4.乘以2运算

[cpp]  view plain  copy
  1. int  mulTwo( int  n){ //计算n*2    
  2.     return  n<<1;  
  3. }  

5.除以2运算

[cpp]  view plain  copy
  1. int  divTwo( int  n){ //负奇数的运算不可用   
  2.     return  n>>1; //除以2   
  3. }  

6.乘以2的m次方

[cpp]  view plain  copy
  1. int  mulTwoPower(int  n, int m){ //计算n*(2^m)   
  2.     return  n<<m;  
  3. }  

7.除以2的m次方

[cpp]  view plain  copy
  1. int  divTwoPower(int  n, int m){ //计算n/(2^m)   
  2.     return  n>>m;  
  3. }  

8.判断一个数的奇偶性

[java]  view plain  copy
  1. boolean  isOddNumber(int  n){  
  2.     return  (n &  1 ) ==  1  
  3. }  

9.不用临时变量交换两个数(面试常考)


C语言 版

[cpp]  view plain  copy
  1. void  swap( int  *a, int  *b){     
  2.     (*a)^=(*b)^=(*a)^=(*b);   
  3. }  

通用版(一些语言中得分开写)

[java]  view plain  copy
  1. a ^= b;  
  2. b ^= a;  
  3. a ^= b;  

10.取绝对值(某些机器上 , 效率比n>0  ?  n:-n 高)

[cpp]  view plain  copy
  1. int  abs( int  n){  
  2. return  (n ^ (n >> 31)) - (n >> 31);  
  3. /* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1  
  4. 若n为正数 n^0=0,数不变,若n为负数有n^-1 需要计算n和-1的补码,然后进行异或运算,  
  5. 结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */   
  6. }  

11.取两个数的最大值(某些机器上, 效率比a>b ? a:b高)


通用版

[cpp]  view plain  copy
  1. int  max( int  a, int  b){  
  2.     return  b&((a-b)>>31) | a&(~(a-b)>>31);  
  3.     /*如果a>=b,(a-b)>>31为0,否则为-1*/  
  4. }  

C语言版

[cpp]  view plain  copy
  1. int  max( int  x, int  y){  
  2.     return  x ^ ((x ^ y) & -(x < y));  
  3.     /*如果x<y x<y返回1,否则返回0,  
  4. 、 与0做与运算结果为0,与-1做与运算结果不变*/   
  5. }  

12.取两个数的最小值(某些机器上, 效率比a>b ? b:a高)


通用版

[cpp]  view plain  copy
  1. int  min( int  a, int  b){  
  2.     return  a&((a-b)>>31) | b&(~(a-b)>>31);  
  3.     /*如果a>=b,(a-b)>>31为0,否则为-1*/  
  4. }  

C语言版

[cpp]  view plain  copy
  1. int  min( int  x, int  y){  
  2.     return  y ^ ((x ^ y) & -(x < y));  
  3.          /*如果x<y x<y返回1,否则返回0,  
  4.             与0做与运算结果为0,与-1做与运算结果不变*/   
  5. }  

13.判断符号是否相同

[java]  view plain  copy
  1. boolean  isSameSign(int  x,  int y){  
  2.     return  (x ^ y) >  0  // true 表示 x和y有相同的符号, false表示x,y有相反的符号。  
  3. }  

14.计算2的n次方

[cpp]  view plain  copy
  1. int  getFactorialofTwo(int  n){ //n > 0  
  2.     return  2<<(n-1); //2的n次方   
  3. }  

15.判断一个数是不是2的幂

[java]  view plain  copy
  1. boolean  isFactorialofTwo(int  n){  
  2.     return  (n & (n -  1 )) ==  0 ;  
  3.     /*如果是2的幂,n一定是100... n-1就是1111....  
  4.        所以做与运算结果为0*/   
  5. }  

16.对2的n次方取余

[java]  view plain  copy
  1. int  quyu( int  m, int  n){ //n为2的次方   
  2.     return  m & (n -  1 );  
  3.     /*如果是2的幂,n一定是100... n-1就是1111....  
  4.      所以做与运算结果保留m在n范围的非0的位*/   
  5. }  

17.求两个整数的平均值

[java]  view plain  copy
  1. int  getAverage( int  x,  int  y){  
  2.         return  (x+y) >>  1   
  3. }  

另一种写法

[java]  view plain  copy
  1. int  getAverage( int  x,  int  y){  
  2.         return  ((x^y) >>  1 ) + (x&y);   
  3.      /*(x^y) >> 1得到x,y其中一个为1的位并除以2,  
  4.        x&y得到x,y都为1的部分,加一起就是平均数了*/   
  5.   
  6. }   

下面是三个最基本对二进制位的操作

18.从低位到高位,取n的第m位

[java]  view plain  copy
  1. int  getBit( int  n,  int  m){  
  2.     return  (n >> (m- 1 )) &  1 ;  
  3. }  

19.从低位到高位.将n的第m位置1

[java]  view plain  copy
  1. int  setBitToOne(int  n,  int m){  
  2.     return  n | ( 1 <<(m- 1 ));  
  3.     /*将1左移m-1位找到第m位,得到000...1...000  
  4.       n在和这个数做或运算*/   
  5. }  

20.从低位到高位,将n的第m位置0

[java]  view plain  copy
  1. int  setBitToZero(int  n,  int m){  
  2.     return  n & ~( 1 <<(m- 1 ));  
  3.     /* 将1左移m-1位找到第m位,取反后变成111...0...1111  
  4.        n再和这个数做与运算*/   



1.获得int型最大值

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.         return  (1<<31) - 1; //2147483647, 由于优先级关系,括号不可省略   
  3. }  

另一种写法

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  -(1<<-1) - 1; //2147483647   
  3. }  

另一种写法

[cpp]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  ~(1<<31); //2147483647   
  3. }  

C语言中不知道int占几个字节时候

[java]  view plain  copy
  1. int  getMaxInt(){  
  2.     return  ((unsigned  int )- 1 ) >>  1 ; //2147483647   
  3. }  

2.获得int型最小值

[cpp]  view plain  copy
  1. int  getMinInt(){  
  2.     return  1<<31; //-2147483648   
  3.  }  

另一种写法

[cpp]  view plain  copy
  1. int  getMinInt(){  
  2.     return  1 << -1; //-2147483648   
  3. }  

3.获得long类型的最大值


C语言版

[cpp]  view plain  copy
  1. long  getMaxLong(){  
  2.     return  ((unsigned  long )-1) >> 1; //2147483647   
  3. }  

JAVA版

[java]  view plain  copy
  1. long  getMaxLong(){  
  2.     return  (( long ) 1 << 127 )- 1 ; //9223372036854775807   
  3. }  
获得long最小值,和其他类型的最大值,最小值同理.

4.乘以2运算

[cpp]  view plain  copy
  1. int  mulTwo( int  n){ //计算n*2    
  2.     return  n<<1;  
  3. }  

5.除以2运算

[cpp]  view plain  copy
  1. int  divTwo( int  n){ //负奇数的运算不可用   
  2.     return  n>>1; //除以2   
  3. }  

6.乘以2的m次方

[cpp]  view plain  copy
  1. int  mulTwoPower(int  n, int m){ //计算n*(2^m)   
  2.     return  n<<m;  
  3. }  

7.除以2的m次方

[cpp]  view plain  copy
  1. int  divTwoPower(int  n, int m){ //计算n/(2^m)   
  2.     return  n>>m;  
  3. }  

8.判断一个数的奇偶性

[java]  view plain  copy
  1. boolean  isOddNumber(int  n){  
  2.     return  (n &  1 ) ==  1 ;  
  3. }  

9.不用临时变量交换两个数(面试常考)


C语言 版

[cpp]  view plain  copy
  1. void  swap( int  *a, int  *b){     
  2.     (*a)^=(*b)^=(*a)^=(*b);   
  3. }  

通用版(一些语言中得分开写)

[java]  view plain  copy
  1. a ^= b;  
  2. b ^= a;  
  3. a ^= b;  

10.取绝对值(某些机器上 , 效率比n>0  ?  n:-n 高)

[cpp]  view plain  copy
  1. int  abs( int  n){  
  2. return  (n ^ (n >> 31)) - (n >> 31);  
  3. /* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1  
  4. 若n为正数 n^0=0,数不变,若n为负数有n^-1 需要计算n和-1的补码,然后进行异或运算,  
  5. 结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */   
  6. }  

11.取两个数的最大值(某些机器上, 效率比a>b ? a:b高)


通用版

[cpp]  view plain  copy
  1. int  max( int  a, int  b){  
  2.     return  b&((a-b)>>31) | a&(~(a-b)>>31);  
  3.     /*如果a>=b,(a-b)>>31为0,否则为-1*/  
  4. }  

C语言版

[cpp]  view plain  copy
  1. int  max( int  x, int  y){  
  2.     return  x ^ ((x ^ y) & -(x < y));  
  3.     /*如果x<y x<y返回1,否则返回0,  
  4. 、 与0做与运算结果为0,与-1做与运算结果不变*/   
  5. }  

12.取两个数的最小值(某些机器上, 效率比a>b ? b:a高)


通用版

[cpp]  view plain  copy
  1. int  min( int  a, int  b){  
  2.     return  a&((a-b)>>31) | b&(~(a-b)>>31);  
  3.     /*如果a>=b,(a-b)>>31为0,否则为-1*/  
  4. }  

C语言版

[cpp]  view plain  copy
  1. int  min( int  x, int  y){  
  2.     return  y ^ ((x ^ y) & -(x < y));  
  3.          /*如果x<y x<y返回1,否则返回0,  
  4.             与0做与运算结果为0,与-1做与运算结果不变*/   
  5. }  

13.判断符号是否相同

[java]  view plain  copy
  1. boolean  isSameSign(int  x,  int y){  
  2.     return  (x ^ y) >  0  // true 表示 x和y有相同的符号, false表示x,y有相反的符号。  
  3. }  

14.计算2的n次方

[cpp]  view plain  copy
  1. int  getFactorialofTwo(int  n){ //n > 0  
  2.     return  2<<(n-1); //2的n次方   
  3. }  

15.判断一个数是不是2的幂

[java]  view plain  copy
  1. boolean  isFactorialofTwo(int  n){  
  2.     return  (n & (n -  1 )) ==  0 ;  
  3.     /*如果是2的幂,n一定是100... n-1就是1111....  
  4.        所以做与运算结果为0*/   
  5. }  

16.对2的n次方取余

[java]  view plain  copy
  1. int  quyu( int  m, int  n){ //n为2的次方   
  2.     return  m & (n -  1 );  
  3.     /*如果是2的幂,n一定是100... n-1就是1111....  
  4.      所以做与运算结果保留m在n范围的非0的位*/   
  5. }  

17.求两个整数的平均值

[java]  view plain  copy
  1. int  getAverage( int  x,  int  y){  
  2.         return  (x+y) >>  1 ;   
  3. }  

另一种写法

[java]  view plain  copy
  1. int  getAverage( int  x,  int  y){  
  2.         return  ((x^y) >>  1 ) + (x&y);   
  3.      /*(x^y) >> 1得到x,y其中一个为1的位并除以2,  
  4.        x&y得到x,y都为1的部分,加一起就是平均数了*/   
  5.   
  6. }   

下面是三个最基本对二进制位的操作

18.从低位到高位,取n的第m位

[java]  view plain  copy
  1. int  getBit( int  n,  int  m){  
  2.     return  (n >> (m- 1 )) &  1 ;  
  3. }  

19.从低位到高位.将n的第m位置1

[java]  view plain  copy
  1. int  setBitToOne(int  n,  int m){  
  2.     return  n | ( 1 <<(m- 1 ));  
  3.     /*将1左移m-1位找到第m位,得到000...1...000  
  4.       n在和这个数做或运算*/   
  5. }  

20.从低位到高位,将n的第m位置0

[java]  view plain  copy
  1. int  setBitToZero(int  n,  int m){  
  2.     return  n & ~( 1 <<(m- 1 ));  
  3.     /* 将1左移m-1位找到第m位,取反后变成111...0...1111  
  4.        n再和这个数做与运算*/   


我们都听说过一句话:“main是C语言的入口”。我至今不明白为什么这么说。就好像如果有人说:“挣钱是泡妞”,肯定无数砖头拍过来。这句话应该是“挣钱是泡妞的一个条件,只不过这个条件特别重要”。那么上面那句话应该是 “main是C语言中一个符号,只不过这个符号比较特别。”
    我们看下面的例子:
    
    /* file name test00.c */
    
    int main(int argc, char* argv)
    {
     return 0;
    }
    
    编译链接它:
    cc test00.c -o test.exe
    会生成 test.exe
    
    但是我们加上这个选项: -nostdlib (不链接标准库)
    cc test00.c -nostdlib -o test.exe
    链接器会报错:
    undefined symbol: __start
    
    也就是说:
    1. 编译器缺省是找 __start 符号,而不是 main
    2. __start 这个符号是程序的起始点
    3. main 是被标准库调用的一个符号
    
    再来思考一个问题:
    我们写程序,比如一个模块,通常要有 initialize 和 de-initialize,但是我们写 C 程序的时候为什么有些模块没有这两个过程么呢?比如我们程序从 main 开始就可以 malloc,free,但是我们在 main 里面却没有初始化堆。再比如在 main 里面可以直接 printf,可是我们并没有打开标准输出文件啊。(不知道什么是 stdin,stdout,stderr 以及 printf 和 stdout 关系的群众请先看看 C 语言中文件的概念)。
    
    有人说,这些东西不需要初始化。如果您真得这么想,请您不要再往下看了,我个人认为计算机软件不适合您。
    聪明的人民群众会想,一定是在 main 之前干了些什么。使这些函数可以直接调用而不用初始化。通常,我们会在编译器的环境中找到一个名字类似于 crt0.o 的文件,这个文件中包含了我们刚才所说的 __start 符号。(crt 大概是 C Runtime 的缩写,请大家帮助确认一下。)
    
    那么真正的 crt0.s 是什么样子呢?下面我们给出部分伪代码:
    
    ///////////////////////////////////////////////////////
    section .text:
    __start:
    
     :
     init stack;
     init heap;
     open stdin;
     open stdout;
     open stderr;
     :
     push argv;
     push argc;
     call _main; (调用 main)
     :
     destory heap;
     close stdin;
     close stdout;
     close stderr;
     :
     call __exit;

    ////////////////////////////////////////////////////
    
    实际上可能还有很多初始化工作,因为都是和操作系统相关的,笔者就不一一列出了。
    注意:
    1. 不同的编译器,不一定缺省得符号都是 __start。
    2. 汇编里面的 _main 就是 C 语言里面的 main,是因为汇编器和C编译器对符号的命名有差异(通常是差一个下划线'_')。
    3. 目前操作系统结构有两个主要的分支:微内核和宏内核。微内核的优点是,结构清晰,简单,内核组件较少,便于维护;缺点是,进程间通信较多,程序频繁进出内核,效率较低。宏内核正好相反。我说这个是什么目的是:没办法保证每个组件都在用户空间(标准库函数)中初始化,有些组件确实可能不要初始化,操作系统在创建进程的时候在内核空间做的。这依赖于操作系统的具体实现,比如堆,宏内核结构可能在内核初始化,微内核结构在用户空间;即使同样是微内核,这个东东也可能会被拿到内核空间初始化。



#ifndef _feed_dog_h //如果到目前为止还没有定义过“_feed_dog_h”这个宏
#define _feed_dog_h //则定义“_feed_dog_h”这个宏

extern void feed_dog(void); //声明一个外部函数

#endif //“#ifndef”到此结束

所以,不管你定义多少次(哪怕你在同一个C文件里定义多次),都不会发生冲突的。


c语言exit和return区别,在fork和vfork中使用

exit函数在头文件stdlib.h中。

exit(0):正常运行程序并退出程序;

exit(1):非正常运行导致退出程序;

return():返回函数,若在main主函数中,则会退出函数并返回一值,可以写为return(0),或return 0。

详细说:

  1. return返回函数值,是关键字;exit是一个函数。

  2. return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
  3. return是函数的退出(返回);exit是进程的退出。

  4. return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。

  5. return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。

  6. 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。

 下面是几个例子:

1.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int  main( void )
{
  pid_t pid;
   int  count=0;
 
  pid=vfork();
   if (pid==0)
  {
     printf ( "child: count=%d\n" ,count);
     printf ( "child: getpid=%d\n" ,getpid());
    count=1;
     printf ( "child: count=%d\n" ,count);
    // return 0;//会出现段错误
    exit(0);//ok
  }
   else
  {
     printf ( "\nfather: pid=%d\n" ,pid);
     printf ( "father: count=%d\n" ,count);
  }
   return (0);
}
 用vfork()创建子进程,执行后程序一直不断地重复运行,不断创建子进程,结尾用exit(0)代替return(0)后问题就能解决
<span style="color:#ff0000;">return 0在一个函数中是正常的返回过程,它会使得程序返回到函数被调用处,回复之前的执行流程,return 语句不会被执行。而exit 一般是在任意位置和使用的,执行到exit 0时,整个进程就over了(这也是为什么在多线程程序中不要随意使用exit的原因),用来使程序退出运行,一般用来处理(不可挽回的)错误状态。</span>

exit和_exit的区别。

当调用exit(0)以后,系统会清洗缓冲区,并且关闭全部的标准I/O流。而调用_exit(0)后缓冲区冲不冲洗得看具体实现,在UNIX系统中,是不清洗的。

一个简单的例子:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. int main()  
  5. {  
  6.         int     value;  
  7.   
  8.         value = 1;  
  9.         printf("%d",value);  
  10.         _exit(0);  
  11. }  

$ ./a.out

$

什么也没有输出来

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. int main()  
  5. {  
  6.         int     value;  
  7.   
  8.         value = 1;  
  9.         printf("%d",value);  
  10.         exit(0);  
  11. }  
./a.out

1$

这里输出1了

由于printf是行缓冲的,行满了或遇到换行符\n时才会将缓冲区数据打印出来,当调用exit(0)时,系统会在结束前将缓冲区的内容都冲洗出来,所以就打印出来了,而调用_exit(0)并不一定会冲洗缓冲区(冲不冲洗得看具体系统实现),所以就可能不打印出来了。


int main(int argc, char *argv[])
{
    char *p = "hello, ", *s = "world!";
    char *t = strcatDemo2(p, s);
    puts(t);
  system("PAUSE");    
  return 0;
}
strcpy函数原型:
复制代码 代码如下:
char *strcpy(char *strDest, const char *strScr)
{
       char *address=strDest;
       assert((strDest != NULL) && (strScr != NULL));
       while(*strScr)                   //是while(*strScr != '\0')的简化形式;
       {
              *strDest++ = *strScr++;
       }
        *strDest = '\0';                       //当strScr字符串长度小于原strDest字符串长度
       return address;                      //时,如果没有改语句,就会出错了。
}


#define ab "hello"
#ifdef ab
#error "ab has been defined."
#endif

<span style="color: rgb(51, 51, 51);">        指令             用途
   </span><span style="color:#ff0000;">      #           空指令,无任何效果
         #include    包含一个源代码文件
         #define     定义宏
         #undef      取消已定义的宏
         #if         如果给定条件为真,则编译下面代码
         #ifdef      如果宏已经定义,则编译下面代码
         #ifndef     如果宏没有定义,则编译下面代码
         #elif       如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
         #endif      结束一个#if……#else<a target=_blank target="_blank" href="https://www.baidu.com/s?wd=%E6%9D%A1%E4%BB%B6%E7%BC%96%E8%AF%91&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1d9mWTvnAfsnHbzuyD1rj0L0AP8IA3qPjfsn1bkrjKxmLKz0ZNzUjdCIZwsrBtEXh9GuA7EQhF9pywdQhPEUiqkIyN1IA-EUBtkrHm3PjTdn6" class="baidu-highlight" rel="nofollow" style="text-decoration: none;">条件编译</a>块
         #error      停止编译并显示错误信息</span>

比如: 
struct string 

char name[8]; 
char sex[4]; 
int age; 
char addr[40]; 

struct string
*student
student
->age = 18;/*给数组中age 赋值。 
借用下*/
student->age = 18;//也可以这样*student.age = 18;
相当於".成员"的作用,但定义成指针时则用"->成员"
struct a
{
int num
int age
}
a
b;
a
* c;
b.num
c->
num
这样好懂了吧.

##、#、#@在C中的作用(VC)

先来段程序:

        
  1. #include <stdio.h>

  2. #define f(a,b) a##b

  3. #define g(a)   #a

  4. #define h(a)   g(a)

  5. #define p(a)   #@a

  6. int main(int argc, char* argv[])

  7. {

  8.    printf("%s\n", g(f(1,2)));

  9.    printf("%s\n", h(f(1,2)));

  10.    printf("%c, %d\n", p(1), p(1));

  11. return 0;

  12. }

输出结果

看上去有点奇怪。

##是连接操作符(我也不知道中文是啥,google到的是token-pasting operator),可以将操作符两端的标识符级联起来。

#是字符串化操作符(stringizing operator),可以将操作符后的标识符转化成字符串。

#@是字符化操作符(charizing),可以将操作符后的数据转化成字符。


预处理过程的几个步骤:

1)字符集转换(如三联字符)

2)断行链接/

3)注释处理,/* comment */,被替换成空格

4)执行预处理命令,如#inlcude、#define、#pragma、#error等

5)转义字符替换

6)相邻字符串拼接

7)将预处理记号替换为词法记号

第4)步即如何展开宏函数的规则:在展开当前宏函数时,如果形参有#或##则不进行宏参数的展开,否则先展开宏参数,再展开当前宏。




1. extern修饰变量的声明。举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

2. extern修饰函数声明。从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

3. 此外,extern修饰符可用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。




内存越界是我们软件开发中经常遇到的一个问题。不经意间的复制常常导致很严重的后果。经常使用memset、memmove、strcpy、strncpy、strcat、sprintf的朋友肯定对此印象深刻,下面就是我个人在开发中实际遇到的一个开发问题,颇具典型。

[cpp]  view plain  copy
  1. #define MAX_SET_STR_LENGTH  50  
  2. #define MAX_GET_STR_LENGTH 100  
  3.   
  4. int* process(char* pMem, int size)  
  5. {  
  6.     char localMemory[MAX_SET_STR_LENGTH] = {0};  
  7.     int* pData = NULL;  
  8.   
  9.     /*  code process */  
  10.     memset(localMemory, 1, MAX_GET_STR_LENGTH);  
  11.     memmove(pMem, localMemory, MAX_GET_STR_LENGTH);  
  12.     return pData;  
  13. }  

    这段代码看上去没有什么问题。我们本意是对localMemory进行赋值,然后拷贝到pMem指向的内存中去。其实问题就出在这一句memset的大小。根据localMemory初始化定义语句,我们可以看出localMemory其实最初的申明大小只有MAX_SET_STR_LENGTH,但是我们赋值的时候,却设置成了MAX_GET_STR_LENGTH。之所以会犯这样的错误,主要是因为MAX_GET_STR_LENGTH和MAX_SET_STR_LENGTH极其相似。这段代码编译后,产生的后果是非常严重的,不断冲垮了堆栈信息,还把返回的int*设置成了非法值。

    那么有没有什么好的办法来处理这样一个问题?我们可以换一个方向来看。首先我们查看,在软件中存在的数据类型主要有哪些?无非就是全局数据、堆数据、栈临时数据。搞清楚了需要控制的数据之后,我们应该怎么对这些数据进行监控呢,一个简单有效的办法就是把memset这些函数替换成我们自己的函数,在这些函数中我们严格对指针的复制、拷贝进行判断和监督。

    (1)事实上,一般来说malloc的数据是不需要我们监督的,因为内存分配的时候,通常库函数会比我们要求的size多分配几个字节,这样在free的时候就可以判断内存的开头和结尾处有没有指针溢出。朋友们可以试一下下面这段代码。

[cpp]  view plain  copy
  1. void heap_memory_leak()  
  2. {  
  3.     char* pMem = (char*)malloc(100);  
  4.     pMem[-1] = 100;  
  5.     pMem[100] = 100;  
  6.     free(pMem);  
  7. }  
    pMem[-1] = 100是堆左溢出, pMem[100]是堆右溢出。

    

    (2)堆全局数据和栈临时数据进行处理时,我们利用memset初始化记录全局指针或者是堆栈临时指针

    a) 首先对memset处理,添加下面一句宏语句

    #define memset(param, value, size)      MEMORY_SET_PROCESS(__FUNCTION__, __LINE__, param, value, size)


    b) 定义内存节点结构

[cpp]  view plain  copy
  1. typedef struct _MEMORY_NODE  
  2. {  
  3.     char functionName[64];  
  4.     int line;  
  5.     void* pAddress;  
  6.     int size;  
  7.     struct _MEMORY_NODE* next;  
  8.   
  9. }MEMORY_NODE;  

    其中functionName记录了函数名称,line记录文件行数, pAddress记录了指针地址, size指向了pAddress指向的内存大小,next指向下一个结构节点。


     c)记录内存节点属性

    在MEMORY_SET_PROCESS处理过程中,不仅需要调用memset函数,还需要对当前内存节点进行记录和保存。可以通过使用单链表节点的方法进行记录。但是如果发现pAddress指向的内存是malloc时候分配过的,此时就不需要记录了,因为堆内存指针溢出的问题lib库已经帮我们解决了。


    d)改造原有内存指针操作函数

    比如对memmove等函数进行改造,不失去一般性,我们就以memmove作为范例。

    添加宏语句 #define memmove(dst, src, size)        MEMMOVE_PROCESS(dst, src, size)

[cpp]  view plain  copy
  1. void MEMMOVE_PROCESS(void* dst, const void* src, int size)  
  2. {  
  3.     MEMORY_NODE* pMemNode = check_node_exist(dst);  
  4.     if(NULL == pMemNode) return;  
  5.   
  6.     assert(dst >= (pMemNode->pAddress));  
  7.     assert(((char*)dst + size) <= ((char*)pMemNode->pAddress + pMemNode->size));  
  8.         memmove(dst, src, size);  
  9.     return;  
  10. }  


    e)下面就是内存节点的删除工作。

    我们知道函数是需要反复使用堆栈的。不同时间相同的堆栈地址对应的是完全不同的指针内容,这就要求我们在函数返回的时候对内存地址进行清理,把内存节点从对应的链表删除。

    我们知道在函数运行后,ebp和esp之间的内存就是通常意义上临时变量的生存空间,所以下面的一段宏就可以记录函数的内存空间。

[cpp]  view plain  copy
  1. #ifdef MEMORY_LEAK_TEST  
  2. #define FUNCTION_LOCAL_SPACE_RECORD()\  
  3. {\  
  4.     int* functionBpRecord = 0;\  
  5.     int*  functionSpRecord = 0;\  
  6. }  
  7. #else  
  8. #define FUNCTION_LOCAL_SPACE_RECORD()  
  9. #endif  
  10.   
  11. #ifdef MEMORY_LEAK_TEST  
  12. #define FUNCTION_LEAVE_PROCESS()\  
  13. {\  
  14. __asm { mov functionBpRecord, bp\  
  15.     mov functionSpRecord, sp}\  
  16.     FREE_MEMORY_NODE(functionBpRecord, functionSpRecord)\  
  17. }  
  18. #else  
  19. #define FUNCTION_LEAVE_PROCESS()  
  20. #endif  

    这两段宏代码,需要插在函数的起始位置和结束的位置,这样在函数结束的时候就可以根据ebp和esp删除堆栈空间中的所有内存,方便了堆栈的重复使用。如果是全局内存,因为函数的变化不会导致地址的变化,所以没有必要进行全局内存节点的处理。

内存溢出检查流程总结:

    (1)对memset进行重新设计,记录除了malloc指针外的一切内存;

    (2)对memmove, strcpy, strncpy,strcat,sprintf等全部函数进行重新设计,因为我们需要对他们的指针运行范围进行判断;

    (3)在函数的开头和结尾位置添加宏处理。函数运行返回前进行节点清除。


  在我们个人编程的过程当中,内存泄露虽然不会像内存溢出那样造成各种莫名奇妙的问题,但是它的危害也是不可忽视的。一方面,内存的泄露导致我们的软件在运行过程中占用了越来越多的内存,占有资源而又得不到及时清理,这会导致我们程序的效率越来越低;另一方面,它会影响我们用户的体验,失去市场的竞争能力。

    常见的内存泄露是这样的:

[cpp]  view plain  copy
  1. void process(int size)  
  2. {  
  3.     char* pData = (char*)malloc(size);  
  4.   
  5.     /* other code  */  
  6.       
  7.     return/* forget to free pData */  
  8. }  
    如上图所示,我们在函数process的处理过程中,每一次都需要对内存进行申请,但是在函数结束的时候却没有进行释放。如果这样的一段代码出现在业务侧,那么后果是难以想象的。举个例子来说,如果我们服务器每秒钟需要接受100个用户的并发访问,每个用户过来的数据,我们都需要本地申请内存重新保存一份。处理结束之后,如果内存没有得到很好地释放,就会导致我们服务器可用的物理内存越来越少。一旦达到某一个临界点之后,操作系统不得不通过内外存的调度来满足我们申请新内存的需求,这在另一方面来讲又会降低服务器服务的质量。

    内存泄露的危害是不言而喻的,但是查找内存泄露却是一件苦难而且复杂的工作。我们都知道,解决bug是一件非常简单的事情,但是寻找bug的出处却是一件非常吃力的事情。因此,我们有必要在自己编写代码的时候,就把查找内存泄露的工作放在很重要的位置上面。那么有没有什么办法来解决这一问题呢?

    我想要做到解决内存泄露,必须做到下面两个方面:

    (1)必须记录内存在哪个函数申请的,具体文件的行数是多少

    (2)内存应该什么时候被释放

   要完成第1个条件其实并不困难。我们可以用节点的方法记录我们申请的内存:

    a)设置节点的数据结构

[cpp]  view plain  copy
  1. typedef struct _MEMORY_NODE  
  2. {  
  3.     char functionName[64];  
  4.     int line;  
  5.     void* pAddress;  
  6.     struct _MEMORY_NODE* next;  
  7.   
  8. }MEMORY_NODE;  
    其中 functionName记录函数名称,line记录行数, pAddress记录分配的地址, next记录下一个内存节点。

    

    b)修改内存的分配函数

    对业务侧的malloc进行函数修改,添加下面一句宏语句

    #define malloc(param)  MemoryMalloc(__FUNCTION__, __LINE__, param)

    在桩函数侧书写下面的代码

[cpp]  view plain  copy
  1. void* MemoryMalloc(const char* name, int line, int size)  
  2. {  
  3.     void* pData = (void*)malloc(size);  
  4.     MEMORY_NODE* pMemNode = NULL;  
  5.     if(NULL == pData) return NULL;  
  6.     memset((char*)pData, 0, size);  
  7.   
  8.     pMemNode = (MEMORY_NODE*)malloc(sizeof(MEMORY_NODE));  
  9.     if(NULL == pMemNode){  
  10.         free(pData);  
  11.         return NULL;  
  12.     }  
  13.     memset((char*)pMemNode, 0, sizeof(MEMORY_NODE));  
  14.     memmove(pMemNode->functionName, name, strlen(name));  
  15.     pMemNode->line = line;  
  16.     pMemNode->pAddress = pData;  
  17.     pMemNode->next = NULL;  
  18.     add_memory_node(pMemNode);  
  19.   
  20.     return pData;  
  21.  

    内存的分配过程中还涉及到了节点的添加,所以我们还需要添加下面的代码

[cpp]  view plain  copy
  1. static MEMORY_NODE* gMemNode = NULL;  
  2.   
  3. void add_memory_node(MEMORY_NODE* pMemNode)  
  4. {  
  5.     MEMORY_NODE* pNode = gMemNode;  
  6.     if(NULL == pMemNode) return;  
  7.     if(NULL == gMemNode){  
  8.         gMemNode = pMemNode;  
  9.         return;  
  10.     }  
  11.   
  12.     while(NULL != pNode->next){  
  13.         pNode = pNode->next;  
  14.     }  
  15.     pNode->next = pMemNode;  
  16.     return;  
  17. }  
    文中gMemNode表示所有内存节点的根节点,我们每增加一次malloc过程就会对内存节点进行记录。在记录过程中,我们还会记录调用malloc的函数名称和具体文件行数,这主要是为了方便我们在后面进行故障定位的时候更好地查找。

   完成了第一个条件之后,我们就要对第二个条件进行完成。

   a)内存什么时候释放,这取决于我们在函数中是怎么实现的,但是我们在编写测试用例的时候却是应该知道内存释放没有,比如说如果测试用例全部结束了,我们有理由相信assert(gMemNode == NULL)这应该是恒等于真的。

    b)内存释放的时候,我们应该做些什么?和节点的添加一样,我们在内存释放的时候需要free指定的内存,free节点,free节点的内存,下面就是在释放的时候我们需要进行的操作


    对业务侧的free函数进行修改,添加下面一句宏代码,

    #define free(param)      MemoryFree(param)


    在桩函数侧输入下面的代码:

[cpp]  view plain  copy
  1. void MemoryFree(void* pAddress)  
  2. {  
  3.     if(NULL == pAddress) return;  
  4.     delete_memory_node(pAddress);  
  5.     free(pAddress);  
  6. }  

    在删除内存的时候,需要删除节点,删除节点的内存

[cpp]  view plain  copy
  1. void delete_memory_node(void* pAddress)  
  2. {  
  3.     MEMORY_NODE* pHead = gMemNode;  
  4.     MEMORY_NODE* pMemNode = gMemNode;  
  5.     while(NULL != pMemNode){  
  6.         if(pAddress == pMemNode->pAddress)  
  7.             break;  
  8.         pMemNode = pMemNode->next;  
  9.     }  
  10.     if(NULL == pMemNode) {  
  11.         assert(1 == 0);  
  12.         return;  
  13.     }  
  14.   
  15.     while(pMemNode != pHead->next){  
  16.         pHead = pHead->next;  
  17.     }  
  18.   
  19.     if(pMemNode == gMemNode){  
  20.         gMemNode = gMemNode->next;  
  21.     }else{  
  22.         pHead->next = pMemNode->next;  
  23.     }  
  24.     free(pMemNode);  
  25.     return;  
  26. }  

    有了上面一小段代码的帮助,我们在编写测试用例的时候,就可以在函数执行后,通过判断内存节点是否为空的方法判断内存是否已经释放。如果内存没有释放,我们还能通过节点的信息帮助我们是哪里发生了错误,但是这个方法 还有两个缺点:

    (1)没有考虑缓存的情况,好多内存分配了之后并不会在函数中马上释放,而是放在缓存池中等待下一次调用,这就需要我们准确把握和判断了。

    (2)代码中节点删除和添加的时候没有考虑多进程的情形,应该考虑用一个互斥锁或者是信号量加以保护。




 不管什么编程语言,一般系统都会额外提供一个基本的函数库。这其中包括字符串处理、文件读写、多线程访问、gui、数据库连接等等。当然上面这些函数库,还是远远不够的,很多时候我们还需要引入大量的第三方函数库。比如加密库aes、校验代码库crc、网络库libevent、xml库、zlib压缩库、正则表达式库、ffmpeg编解码库、webkit引擎库、openssl库。很多的库函数不仅被业界广泛使用,而且质量也有保证。当然,出现问题有时候也比较普遍,openssl就是一个范例。

有过C语言编程的朋友应该都有过指针越界的困扰。不管越界的地方是全局地址、还是局部地址,查起来都是非常麻烦,原因大多时候都来自于自己对char数组类型的误用。很多同学可能都不是很清楚,在str系类的函数中,函数会在结尾的时候添加'\0'。比如说strncpy函数,在linux kernel上是这样写的,

 

[cpp]  view plain  copy
  1. /** 
  2.  * strncpy - Copy a length-limited, %NUL-terminated string 
  3.  * @dest: Where to copy the string to 
  4.  * @src: Where to copy the string from 
  5.  * @count: The maximum number of bytes to copy 
  6.  * 
  7.  * The result is not %NUL-terminated if the source exceeds 
  8.  * @count bytes. 
  9.  */  
  10. char * strncpy(char * dest,const char *src,size_t count)  
  11. {  
  12.     char *tmp = dest;  
  13.   
  14.     while (count) {  
  15.         if ((*tmp = *src) != 0) src++;  
  16.         tmp++;  
  17.         count--;  
  18.     }  
  19.     return dest;  
  20. }  

    而memmove函数是这样描写的,

 

[cpp]  view plain  copy
  1. /** 
  2.  * memmove - Copy one area of memory to another 
  3.  * @dest: Where to copy to 
  4.  * @src: Where to copy from 
  5.  * @count: The size of the area. 
  6.  * 
  7.  * Unlike memcpy(), memmove() copes with overlapping areas. 
  8.  */  
  9. void * memmove(void * dest,const void *src,size_t count)  
  10. {  
  11.     char *tmp, *s;  
  12.   
  13.     if (dest <= src) {  
  14.         tmp = (char *) dest;  
  15.         s = (char *) src;  
  16.         while (count--)  
  17.             *tmp++ = *s++;  
  18.         }  
  19.     else {  
  20.         tmp = (char *) dest + count;  
  21.         s = (char *) src + count;  
  22.         while (count--)  
  23.             *--tmp = *--s;  
  24.         }  
  25.   
  26.     return dest;  
  27. }  

    通过上面的代码,我们发现memmove函数有几个好处,

    (1)会根据dest和src的地址关系灵活判断复制顺序;

    (2)不会额外添加'\0';

    (3)需要自己时刻关心当前字符串的大小问题;

    (4)memmove可以应用于各种数据结构,而不仅仅是char*类型。


学习如何编译linux 内核,其实步骤也不复杂

    a)解压linux内核版本

    b)cd linux目录

    c)cp /boot/config-2.6.32-220.el6.i686 .config

    d) make menuconfig

    e)保存,直接exit退出

    f)make bzImage

    g) make modules

    h) make modules_install

    i) make install







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值