• 02--C编程细节整理(一)


    C语言比较多,这篇是平时攒下的。有些内容在工作后可能会很常见,但是不用容易忘,所以就写篇博客吧。

    1.        printf的用法

    %*可以用来跳过字符,可以用于未知缩进。像下面一样.

    for(i = 1; i < 10; i++)

    {

    printf("%*c %*c ",  9 - abs(i - 5), '*', abs(i - 5) + 1, '*');

    }

    %[]可以用来读取指定的内容,%[^]可以用来忽略指定内容(正则表达式?)

    %m可以不带参数,输出产生的错误信息

    2.        .关于define的用法

    #是将后面所跟随内容字符串化,##则是将前后内容连接起来

    还有linux下各种多层宏定义。。。宏定义可谓博大精深啊。。

    3.        .关于setjmplongjmp

    目前在linux多线程的头文件pthreadtypes.h中包含了此函数的头文件。

    这个主要作用是用来从异常恢复的.也可以做"软重启".

    因为setjmp把环境变量的设置和栈的值都保存在参数env数组中,longjmp,根据其值来恢复setjmp时的位置.

    这个env通常是一个全局变量.毕竟这是要跨越函数的.

    4.        #include

    其实这个东西可以写在任何的地方,你可以试试将main里面代码写在某个文件中,然后在main中包含这个文件。代码照样运行。

    5.        .sizeof

    sizeof一个数组名的时候,如果数组和sizeof在同一个代码块中,那么返回数组长,否则返回指针大小。

    (当数组名传递给函数的时候传递的仅仅是个指针)

    6.        .volatile

    让每次读取数据都从内存中读取,而不是在其他位置。可以防止部分编译器优化。在嵌入式中和多线程中有所应用。

    最近学linux发现还可以用用来描述自己定义的锁!从而避免了编译器直接把锁优化了。

    7.        main函数的奇妙用法.

    其实main函数是可以递归的。

    8.        关于||&&

    其实||的优先级比&&低。

    9.        如何让返回值有多重类型?

    union定义一个结构体,那么就可以选择返回多种类型了.

    10.    为何通常c中的指针大小为4个字节?

    一种可能是CPU的地址线只有32.另一种是由于最大程序限制在4G=2^32B来确定的.

    11.    当函数返回double值的时候是80位的.

    所以在返回值赋给一个double变量的时候会损失精度.

    具体的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value

    12.    . __cdecl关键字

    这个关键字是用来定义函数参数入栈顺序从右向左的.并且我在VC上测试的时候发现是先计算再入栈.这个估计用的很少吧..

    刚学AT&T32位汇编,发现其实是用pushl来将参数逐一入栈,然后弹出传给函数的.估计加了__cdecl的话就会使用pushl的方式来传值吧.

    貌似这样就只能是从右到左传参数给函数啊....还需学习..

    13.    内存对齐

    结构体和联合体中,比如struct{int a;char b; double c};那么这个结构体大小为16(32位机),不是13.这一点我经常忘掉..但是发现这个错误的时候又很容易想起来..

    14.    位段

    这个是11位的来分配的..(原来还以为是按字节来的..)

    这个东西刚开始觉得似乎蛮有用.但是似乎用途只有在网络协议需要这么斤斤计较..

    百度百科上说只能是unsignedint类型指定,但是我在dev c++上写c程序,也可以用char类型.

    并且这个后面:加的值不能超过原来的类型的大小.

    考虑到存储器的对齐和效率应该应用不多.

    15.    关于union的用法

    其实这个可以用来显示一些不能见的内容..比方说将一个指针和一个int定义在一起..如果指针是不能见的,那么可以通过以int的形式来访问..

    union a{

    point A;

    };

    如果A不能直接访问,那么就可以用B来访问。也算是编译器的漏洞吧。

    我记得C++好像有一种类型指针是这样的,不允许见到其值.但是可以用int来知道其值.

    原先刷题还遇到过一个问题.VC不能用long long类型的于是想用union{char a[8];int a}.这样来变成long long..结果printf貌似只能识别到32位长度的大小..

    所以还是老老实实去linux下编译了...

    16.    关于typedefconst联合使用时一个值得注意的地方

    typedef char* zifu;

    const zifu a;

    那么a是个什么样的指针呢?是不能改变所指向地址的值还是不能改变自身?

    这个问题我一开是也想错了...想成用define的效果了...其实上面的定义等价于

    char* const a;

    也就是说不能改变指针自身,但是可以改变所指向的值..typedefdefine相比会扩展一些内容.

    17.    .关于typedef的其他容易忘记的方法.

    函数指针 typedef int (*)(char ) a;

    那么 a c; c就是一个返回值为int,参数为char类型的函数指针.

    避免弄成int (*)(*f)(int ,char)(char)这种一团麻乱的样子..很容易就看错的..

    申请数组 typedef int int_shuzu[10];

    那么int_shuzu a; a就是一个数组指针了..这种很容易看不懂的..

    18.    关于参数传递

    void fun(a,b,c)

    int a,b,c;

    {}

    你很可能没见过这种传值方式,不过它确实是可以编译成功的。可以少写几个int~

    19.    assert

    C的异常处理宏。最近才发现有这个东西。这个比try--catch简单多了。用于判断某个条件是否满足,不满足的话跳转到自己的函数上来显示信息。用来调试程序很方便的。建议学习~

    20.    关于如何把递归用函数指针来代替的方式.

    这个方法很奇葩..感觉还是能有点用的.

    #include <stdio.h>

    typedef int (*PFUN)(int);

    PFUN ptr[2];

    int end(int a){

      return 0;

    }

    int sum(int a){

      return a + ptr[!!a](a - 1);

    }

    int main(){

      int T, m;

      scanf("%d", &T);

      while(T--){

        ptr[0] = end;

        ptr[1] = sum;

        scanf("%d", &m);

        printf("%d ", sum(m));

      }

      return 0;

    }

    这是通过函数指针加位运算来模仿递归...目标是求1+2+...+n..

    两次逻辑取反刚好把大于1的数转为1,原来为零仍然为零.

    这个技巧就当作好玩吧~~

    21.    字符串常量

    sizeof( 'a' );你猜猜是几?结果是4!!!!这是C里面估计很少人注意到的差别..当然也没什么用就是了..(C++里面是1,但是C++还有'aa'这种单引号里面两个字符的情况..结果是4)

    22.    .强制转换

    这是我看一个opencv程序中发现的。你可能永远都只将强制转换用于赋值符号的右边,但其实在左边也是可以的.

    就向这样

    ((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];

    23.    重定向调试

    freopen("文件名""r",stdin);写在所有从输入流读取信息的函数之前。

    将标准输入重定向为文件中的内容。对于那些喜欢刷ACM的人来说,用了这个函数调试真是方便多了啊。

    对于stdin这个是操作系统默认分给C程序的3个流指针之一,linux下貌似文件描述符号0就是指stdin。当然还有12,分别是stdout,stderr

    三个指针定义在stdio.h中。

    24.    关于指针与int类型

    其实int类型可以赋值给指针类型!!

    以前一直记得是必须加强制转换的,但是在一本书上看到居然可以直接这样!!

    只是给个warming!!这对于底层程序员是多么方便的事情啊!!!

    dev c++VC上测试通过。还可以换成short*double*long*~

    int i;

    char*p;

    i=1354;

    p = i;

    25.    关于宏函数与逗号表达式

    最近看操作系统内核源码发现的。。

    其实逗号表达式和宏是一对好基友啊~不仅让宏函数的功能更多,还可以让宏函数带上返回值!

    并且和内联函数一样高效率!!

    好比说交换ab两个值并且返回它们的和的宏函数,可以这样写:

    #define  swap(a,b,c)   (c=a,a=b,b=c,a+b)

    这样只用保证abc三个变量都是同一类型即可,就不用写那么多函数了~~

    是不是有点类似C++的模版啊~~~还提高了不少开发效率呢~~~

    由于是宏函数,所以就无法用于下面的回调函数中了,不过宏函数本身就这么方便和高效,这点缺点还是可以容忍的.

    26.    .回调函数

    原来一直用qsort,但是不知道还有回调函数这一名字.

    这就有点像C++中的泛型算法了.可以把判断的部分通过函数封装起来.

    将判断方法通过参数的方式传递给函数,然后实现泛型~~

    void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

    注意最后一个参数就是排序的条件判断函数,也就是回调函数。

     类似的还有多线程中指定的入口函数。

     

    27.    关于_exitexit的差别

     

    _exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。

    exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容.

     

    这实在其他地方直接抄来的.linux下如果fork子进程来做点事的话可能会用到.

    28.    .关于int a[0];的用法。

     

    原来学c的时候弄过这个,但只是当作实验。。没想到还真的有人会用这个东西。

    主要用来当作内存地址的标签用。可以通过编译器来记住指针地址,但是不会消耗实际内存。

    只有当分配空间之后才能使用。这是得多么节约空间才会这么写啊。。。。

    注意,这貌似是C99标准才支持的一个用法,并且 gcc编译的时候需要带上-pedantic选项。

    29.    关于unsigned整数做循环变量

    unsigned  0-1答案的二进制码是全一啊!!!

    原来写单片机程序就被坑过..编译器虽然会警告,但是通常都被我忽略了...

    记住,只要是for中的循环变量比较,要么全是有符号的,要么全是无符号的!!

    30.    关于为什么要使用二进制读文件和写文件

    这是学python的时候晓得的。。当你的文件保存为二进制的话,就可以用你的方法来解析文件。

    如果不用二进制,那么就得看操作系统了。所以,对于保存独有的数据,多用二进制保存吧。话说数据库的数据文件都是这种方式读写文件的吧。

    而且二进制也不容被人了解。。好多游戏的文件都是明文的呢。。。

    31.    .sizeof(表达式)的问题

    sizeof的表达式并不计算!!!只得出是什么类型的而已!!

    int a=1;

    printf("%d ",sizeof(a=2));

    printf("%d ",a);

    结果是41

    测试环境dev c++

    32.    三目运算符与if/else哪个效率高?

    个人在gcc下测试,发现汇编没有差别。不知道其他平台是否也是这样。当然我测试的方式可能不对。下文还会再说三目运算符的。

    CSAPP中说gcc会优化三目运算符。所以估计在某些特定情况下效率比if高点。

    33.    .一种简单的宏交换

     

    #define  swapa,b   a ^= b^= a^= b

     

    多么简洁啊~虽然以前也写过这种异或交换,但是从来想过还能用连等啊~~

    34.    long double

    这是C99标准中的新类型.<深入理解计算机系统>里面说是扩展浮点数.80个二进制位.

    80位刚好对应intelCPU中的FPU.我的ATT汇编总结中讲过这个寄存器,原来以为只是浮点返回值和浮点运算中间过程用到.

    现在看来还可以当作变量用.需要注意的是在gcc下一般是会按4字节对齐的,所以不是10个字节,而是12个字节.

    35.  关于<符号

    这是看CSAPP上的.<符号对于分支跳转指令来说预测的准确率并不是特别的好.

    所以刚好想到c++中循环的条件判断用!=的习惯.虽然C++ primer上说是因为迭代器可能在循环中改变的原因,但是从这个来看还是养成写!=的习惯比较好.

    可能得到的效率不一定提高特别多,但是对于高并发高吞吐量的系统来说,我猜这点细微的差别还是有变化的.

    还是养成写!=的习惯吧~

    36.   关于三目运算符

    一般情况下没什么问题,但是在某些编译器下,对于   xp?*xp:0这种xpNULL指针的时候就不能用了,否则会造成内存错误!

    这时因为编译器对三目运算符进行了优化。先算出后面两个表达式的结果,然后再来判断条件。

    不过我用gcc测试了一下,发现gcc不是这样的,所以在gcc下这么用没什么问题。

    以后见到这种情况别奇怪就是了~

    37.    .关于宏函数中使用 ###

    之前一直不晓得这有什么实际用处,但是在描述路径的时候很有用.

    好比说你之前文件在inc/现在改成在incc/.那么完全可以写一个函数用来拼接

    #define  f(PATH,FILE)   # x##y

    这种形式.那么更换文件目录的时候就很方便了.

    38.    .关于定义多个同名全局变量

    在同一文件中是不能定义多个同名全局变量的,但是在多个文件中就可以。

    CSAPP上写的.gcc的连接器在linking的时候额会把全局变量分为弱和强两种状态.

    并且优先选择强状态,但可能类型会存在问题

    好比在A文件如下定义了xy

    void other();

    int x=112233;

    int y=332211;

     

    int main(){

         prntf("x=0x%x  y=0x%x ",x,y);

         other();

         prntf("x=0x%x  y=0x%x ",x,y);

    }

    在另一个文件中也定义了x,但类型是double

    double x

     

    void other(){

       x=-0.0;

    }

    然后gcc 两个文件。有一个警告。暂时先不管。

    你运行了之后会发现,y的值居然变了!

    也就是说,在other中,仍然认为xdouble类型的!

    double由于是8字节而int4字节,给x赋值会覆盖原来的y

    表示如果other函数中换成纯粹的0.那么y就是0

    39.    .restrict关键字

    这个常常忘记是什么意思...C99新加的标准.让编译器翻译的时候,对其内存的操作只能通过其指针来完成.方便编译器优化.

    不过C++里面好像还没有这个关键字.

    40.    .constdefine定义一个常量的区别

    const指定的是一个变量,define相当于直接替换.

    const是可以带指定类型的,define则是默认类型的.

    同时const定义的变量还可以用取地址符&来得到其值.

    41.    关于GCC内联汇编

    用一般的asm volatile()形式的话,在默认情况下是没什么问题的。但是如果在编译条件中加上了-std=c99的话则不行。

    具体我也不清楚怎么搞。。没查到什么有用的东西。自己试过在__asm__volatile___asm volatile这些形式都不行。

    有高手会的话还请指教!

    42.    .关于GNU C__attribute__

    这个是GNU C的一个特色。可以设置函数,变量,以及类型的属性值。通常不用管这个东西,但是有一些软件有要求会需要用到这个。

    具体我就不展开了,百度可以搜出很多内容。这里提一个__attribute__(( aligned(n))).在结构体后面加上后可以按照n字节对齐。默认按最大变量字节对齐。

    43.    .关于可变参数宏的实现(va_arg.

    有一部分靠的是编译器支持"..."这种参数形式,然后保证不等长参数都可以完全入栈。实际上,根据C标准的入栈规则,参数是依次从右往左入栈的。只要顺着esp寄存器来找,那么就可以依次读出参数的其值。需要注意的是读出多少个字节,不然栈就乱了。所以必须在读出的时候加上参数类型。printf实现中也是先根据字符串中的类型再读的。我个人实测后确实如此~

     

    测试结果如下,Linuxgcc编译。

     

    说明: http://img2.tuicool.com/AN7Fr2E.png!web

    如图所示,显然可以看到printf中实现的算法是先根据字符串对应的格式来从内存中读取的。也就是说如果类型选错了,内容显示一定乱了。

    44.    关于返回结构体

    最近看编译器实现方面的东西,貌似C++那种传递性的使用(如cout<< xxx<<<xx;这种)应该是不难实现的。然后我就上测试了下,我发现GCC下函数返回结构体是可以直接在后面加上.成员来访问成员的。。结果如下

    说明: http://img1.tuicool.com/ERVZFn.png!web

    45.    关于**指针和(*)[]指针

    我发现很多人还是不清楚**指针和(*)[]指针的差别。第一个我就叫它双层指针,第二个叫行指针。有人喜欢把第一个叫二维指针吧,如果这么叫很容易晕。

    实际上前者是一个指向指针的指针,而后者是一个带长度的指针。前者可以指向后者。后者与一般指针的差别就是你p++的时候是移动多个变量长度!其他指针只移动一个变量长度。这里的长度说的是存储变量地址的大小。前者指向一个指针变量的地址,而后者多用于指向一个二维数组的地址。所以传递二维数组参数的时候,用后者!

    46.    关于%模运算

    其实是可以模负数的!但和数学定义不同,对于负数的模仍然带有符号。

     

     

    C语言比较多,这篇是平时攒下的。有些内容在工作后可能会很常见,但是不用容易忘,所以就写篇博客吧。

    1.        printf的用法

    %*可以用来跳过字符,可以用于未知缩进。像下面一样.

    for(i = 1; i < 10; i++)

    {

    printf("%*c %*c ",  9 - abs(i - 5), '*', abs(i - 5) + 1, '*');

    }

    %[]可以用来读取指定的内容,%[^]可以用来忽略指定内容(正则表达式?)

    %m可以不带参数,输出产生的错误信息

    2.        .关于define的用法

    #是将后面所跟随内容字符串化,##则是将前后内容连接起来

    还有linux下各种多层宏定义。。。宏定义可谓博大精深啊。。

    3.        .关于setjmplongjmp

    目前在linux多线程的头文件pthreadtypes.h中包含了此函数的头文件。

    这个主要作用是用来从异常恢复的.也可以做"软重启".

    因为setjmp把环境变量的设置和栈的值都保存在参数env数组中,longjmp,根据其值来恢复setjmp时的位置.

    这个env通常是一个全局变量.毕竟这是要跨越函数的.

    4.        #include

    其实这个东西可以写在任何的地方,你可以试试将main里面代码写在某个文件中,然后在main中包含这个文件。代码照样运行。

    5.        .sizeof

    sizeof一个数组名的时候,如果数组和sizeof在同一个代码块中,那么返回数组长,否则返回指针大小。

    (当数组名传递给函数的时候传递的仅仅是个指针)

    6.        .volatile

    让每次读取数据都从内存中读取,而不是在其他位置。可以防止部分编译器优化。在嵌入式中和多线程中有所应用。

    最近学linux发现还可以用用来描述自己定义的锁!从而避免了编译器直接把锁优化了。

    7.        .main函数的奇妙用法.

    其实main函数是可以递归的。

    8.        .关于||&&

    其实||的优先级比&&低。

    9.        ,如何让返回值有多重类型?

    union定义一个结构体,那么就可以选择返回多种类型了.

    10.    .为何通常c中的指针大小为4个字节?

    一种可能是CPU的地址线只有32.另一种是由于最大程序限制在4G=2^32B来确定的.

    11.    .当函数返回double值的时候是80位的.

    所以在返回值赋给一个double变量的时候会损失精度.

    具体的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value

    12.    . __cdecl关键字

    这个关键字是用来定义函数参数入栈顺序从右向左的.并且我在VC上测试的时候发现是先计算再入栈.这个估计用的很少吧..

    刚学AT&T32位汇编,发现其实是用pushl来将参数逐一入栈,然后弹出传给函数的.估计加了__cdecl的话就会使用pushl的方式来传值吧.

    貌似这样就只能是从右到左传参数给函数啊....还需学习..

    13.    内存对齐

    结构体和联合体中,比如struct{int a;char b; double c};那么这个结构体大小为16(32位机),不是13.这一点我经常忘掉..但是发现这个错误的时候又很容易想起来..

    14.    .位段

    这个是11位的来分配的..(原来还以为是按字节来的..)

    这个东西刚开始觉得似乎蛮有用.但是似乎用途只有在网络协议需要这么斤斤计较..

    百度百科上说只能是unsignedint类型指定,但是我在dev c++上写c程序,也可以用char类型.

    并且这个后面:加的值不能超过原来的类型的大小.

    考虑到存储器的对齐和效率应该应用不多.

    15.    .关于union的用法

    其实这个可以用来显示一些不能见的内容..比方说将一个指针和一个int定义在一起..如果指针是不能见的,那么可以通过以int的形式来访问..

    union a{

    point A;

    };

    如果A不能直接访问,那么就可以用B来访问。也算是编译器的漏洞吧。

    我记得C++好像有一种类型指针是这样的,不允许见到其值.但是可以用int来知道其值.

    原先刷题还遇到过一个问题.VC不能用long long类型的于是想用union{char a[8];int a}.这样来变成long long..结果printf貌似只能识别到32位长度的大小..

    所以还是老老实实去linux下编译了...

    16.    .关于typedefconst联合使用时一个值得注意的地方

    typedef char* zifu;

    const zifu a;

    那么a是个什么样的指针呢?是不能改变所指向地址的值还是不能改变自身?

    这个问题我一开是也想错了...想成用define的效果了...其实上面的定义等价于

    char* const a;

    也就是说不能改变指针自身,但是可以改变所指向的值..typedefdefine相比会扩展一些内容.

    17.    .关于typedef的其他容易忘记的方法.

    函数指针 typedef int (*)(char ) a;

    那么 a c; c就是一个返回值为int,参数为char类型的函数指针.

    避免弄成int (*)(*f)(int ,char)(char)这种一团麻乱的样子..很容易就看错的..

    申请数组 typedef int int_shuzu[10];

    那么int_shuzu a; a就是一个数组指针了..这种很容易看不懂的..

    18.    .关于参数传递

    void fun(a,b,c)

    int a,b,c;

    {}

    你很可能没见过这种传值方式,不过它确实是可以编译成功的。可以少写几个int~

    19.    .assert

    C的异常处理宏。最近才发现有这个东西。这个比try--catch简单多了。用于判断某个条件是否满足,不满足的话跳转到自己的函数上来显示信息。用来调试程序很方便的。建议学习~

    20.    关于如何把递归用函数指针来代替的方式.

    这个方法很奇葩..感觉还是能有点用的.

    #include <stdio.h>

    typedef int (*PFUN)(int);

    PFUN ptr[2];

    int end(int a){

      return 0;

    }

    int sum(int a){

      return a + ptr[!!a](a - 1);

    }

    int main(){

      int T, m;

      scanf("%d", &T);

      while(T--){

        ptr[0] = end;

        ptr[1] = sum;

        scanf("%d", &m);

        printf("%d ", sum(m));

      }

      return 0;

    }

    这是通过函数指针加位运算来模仿递归...目标是求1+2+...+n..

    两次逻辑取反刚好把大于1的数转为1,原来为零仍然为零.

    这个技巧就当作好玩吧~~

    21.    字符串常量

    sizeof( 'a' );你猜猜是几?结果是4!!!!这是C里面估计很少人注意到的差别..当然也没什么用就是了..(C++里面是1,但是C++还有'aa'这种单引号里面两个字符的情况..结果是4)

    22.    .强制转换

    这是我看一个opencv程序中发现的。你可能永远都只将强制转换用于赋值符号的右边,但其实在左边也是可以的.

    就向这样

    ((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];

    23.    重定向调试

    freopen("文件名""r",stdin);写在所有从输入流读取信息的函数之前。

    将标准输入重定向为文件中的内容。对于那些喜欢刷ACM的人来说,用了这个函数调试真是方便多了啊。

    对于stdin这个是操作系统默认分给C程序的3个流指针之一,linux下貌似文件描述符号0就是指stdin。当然还有12,分别是stdout,stderr

    三个指针定义在stdio.h中。

    24.    关于指针与int类型

    其实int类型可以赋值给指针类型!!

    以前一直记得是必须加强制转换的,但是在一本书上看到居然可以直接这样!!

    只是给个warming!!这对于底层程序员是多么方便的事情啊!!!

    dev c++VC上测试通过。还可以换成short*double*long*~

    int i;

    char*p;

    i=1354;

    p = i;

    25.    关于宏函数与逗号表达式

    最近看操作系统内核源码发现的。。

    其实逗号表达式和宏是一对好基友啊~不仅让宏函数的功能更多,还可以让宏函数带上返回值!

    并且和内联函数一样高效率!!

    好比说交换ab两个值并且返回它们的和的宏函数,可以这样写:

    #define  swap(a,b,c)   (c=a,a=b,b=c,a+b)

    这样只用保证abc三个变量都是同一类型即可,就不用写那么多函数了~~

    是不是有点类似C++的模版啊~~~还提高了不少开发效率呢~~~

    由于是宏函数,所以就无法用于下面的回调函数中了,不过宏函数本身就这么方便和高效,这点缺点还是可以容忍的.

    26.    .回调函数

    原来一直用qsort,但是不知道还有回调函数这一名字.

    这就有点像C++中的泛型算法了.可以把判断的部分通过函数封装起来.

    将判断方法通过参数的方式传递给函数,然后实现泛型~~

    void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

    注意最后一个参数就是排序的条件判断函数,也就是回调函数。

     类似的还有多线程中指定的入口函数。

     

    27.    关于_exitexit的差别

     

    _exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。

    exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容.

     

    这实在其他地方直接抄来的.linux下如果fork子进程来做点事的话可能会用到.

    28.    .关于int a[0];的用法。

    原来学c的时候弄过这个,但只是当作实验。。没想到还真的有人会用这个东西。

    主要用来当作内存地址的标签用。可以通过编译器来记住指针地址,但是不会消耗实际内存。

    只有当分配空间之后才能使用。这是得多么节约空间才会这么写啊。。。。

    注意,这貌似是C99标准才支持的一个用法,并且 gcc编译的时候需要带上-pedantic选项。

    29.    关于unsigned整数做循环变量

    unsigned  0-1答案的二进制码是全一啊!!!

    原来写单片机程序就被坑过..编译器虽然会警告,但是通常都被我忽略了...

    记住,只要是for中的循环变量比较,要么全是有符号的,要么全是无符号的!!

    30.    关于为什么要使用二进制读文件和写文件

    这是学python的时候晓得的。。当你的文件保存为二进制的话,就可以用你的方法来解析文件。

    如果不用二进制,那么就得看操作系统了。所以,对于保存独有的数据,多用二进制保存吧。话说数据库的数据文件都是这种方式读写文件的吧。

    而且二进制也不容被人了解。。好多游戏的文件都是明文的呢。。。

    31.    .sizeof(表达式)的问题

    sizeof的表达式并不计算!!!只得出是什么类型的而已!!

    int a=1;

    printf("%d ",sizeof(a=2));

    printf("%d ",a);

    结果是41

    测试环境dev c++

    32.    三目运算符与if/else哪个效率高?

    个人在gcc下测试,发现汇编没有差别。不知道其他平台是否也是这样。当然我测试的方式可能不对。下文还会再说三目运算符的。

    CSAPP中说gcc会优化三目运算符。所以估计在某些特定情况下效率比if高点。

    33.    .一种简单的宏交换

     

    #define  swapa,b   a ^= b^= a^= b

     

    多么简洁啊~虽然以前也写过这种异或交换,但是从来想过还能用连等啊~~

    34.    long double

    这是C99标准中的新类型.<深入理解计算机系统>里面说是扩展浮点数.80个二进制位.

    80位刚好对应intelCPU中的FPU.我的ATT汇编总结中讲过这个寄存器,原来以为只是浮点返回值和浮点运算中间过程用到.

    现在看来还可以当作变量用.需要注意的是在gcc下一般是会按4字节对齐的,所以不是10个字节,而是12个字节.

    35.    .关于<符号

    这是看CSAPP上的.<符号对于分支跳转指令来说预测的准确率并不是特别的好.

    所以刚好想到c++中循环的条件判断用!=的习惯.虽然C++ primer上说是因为迭代器可能在循环中改变的原因,但是从这个来看还是养成写!=的习惯比较好.

    可能得到的效率不一定提高特别多,但是对于高并发高吞吐量的系统来说,我猜这点细微的差别还是有变化的.

    还是养成写!=的习惯吧~

    36.    .关于三目运算符

    一般情况下没什么问题,但是在某些编译器下,对于   xp?*xp:0这种xpNULL指针的时候就不能用了,否则会造成内存错误!

    这时因为编译器对三目运算符进行了优化。先算出后面两个表达式的结果,然后再来判断条件。

    不过我用gcc测试了一下,发现gcc不是这样的,所以在gcc下这么用没什么问题。

    以后见到这种情况别奇怪就是了~

    37.    .关于宏函数中使用 ###

    之前一直不晓得这有什么实际用处,但是在描述路径的时候很有用.

    好比说你之前文件在inc/现在改成在incc/.那么完全可以写一个函数用来拼接

    #define  f(PATH,FILE)   # x##y

    这种形式.那么更换文件目录的时候就很方便了.

    38.    .关于定义多个同名全局变量

    在同一文件中是不能定义多个同名全局变量的,但是在多个文件中就可以。

    CSAPP上写的.gcc的连接器在linking的时候额会把全局变量分为弱和强两种状态.

    并且优先选择强状态,但可能类型会存在问题

    好比在A文件如下定义了xy

    void other();

    int x=112233;

    int y=332211;

     

    int main(){

         prntf("x=0x%x  y=0x%x ",x,y);

         other();

         prntf("x=0x%x  y=0x%x ",x,y);

    }

    在另一个文件中也定义了x,但类型是double

    double x

     

    void other(){

       x=-0.0;

    }

    然后gcc 两个文件。有一个警告。暂时先不管。

    你运行了之后会发现,y的值居然变了!

    也就是说,在other中,仍然认为xdouble类型的!

    double由于是8字节而int4字节,给x赋值会覆盖原来的y

    表示如果other函数中换成纯粹的0.那么y就是0

    39.    .restrict关键字

    这个常常忘记是什么意思...C99新加的标准.让编译器翻译的时候,对其内存的操作只能通过其指针来完成.方便编译器优化.

    不过C++里面好像还没有这个关键字.

    40.    .constdefine定义一个常量的区别

    const指定的是一个变量,define相当于直接替换.

    const是可以带指定类型的,define则是默认类型的.

    同时const定义的变量还可以用取地址符&来得到其值.

    41.    关于GCC内联汇编

    用一般的asm volatile()形式的话,在默认情况下是没什么问题的。但是如果在编译条件中加上了-std=c99的话则不行。

    具体我也不清楚怎么搞。。没查到什么有用的东西。自己试过在__asm__volatile___asm volatile这些形式都不行。

    有高手会的话还请指教!

    42.    .关于GNU C__attribute__

    这个是GNU C的一个特色。可以设置函数,变量,以及类型的属性值。通常不用管这个东西,但是有一些软件有要求会需要用到这个。

    具体我就不展开了,百度可以搜出很多内容。这里提一个__attribute__(( aligned(n))).在结构体后面加上后可以按照n字节对齐。默认按最大变量字节对齐。

    43.    .关于可变参数宏的实现(va_arg.

    有一部分靠的是编译器支持"..."这种参数形式,然后保证不等长参数都可以完全入栈。实际上,根据C标准的入栈规则,参数是依次从右往左入栈的。只要顺着esp寄存器来找,那么就可以依次读出参数的其值。需要注意的是读出多少个字节,不然栈就乱了。所以必须在读出的时候加上参数类型。printf实现中也是先根据字符串中的类型再读的。我个人实测后确实如此~

     

    测试结果如下,Linuxgcc编译。

     

    说明: http://img2.tuicool.com/AN7Fr2E.png!web

    如图所示,显然可以看到printf中实现的算法是先根据字符串对应的格式来从内存中读取的。也就是说如果类型选错了,内容显示一定乱了。

    44.    关于返回结构体

    最近看编译器实现方面的东西,貌似C++那种传递性的使用(如cout<< xxx<<<xx;这种)应该是不难实现的。然后我就上测试了下,我发现GCC下函数返回结构体是可以直接在后面加上.成员来访问成员的。。结果如下

    说明: http://img1.tuicool.com/ERVZFn.png!web

    45.    关于**指针和(*)[]指针

    我发现很多人还是不清楚**指针和(*)[]指针的差别。第一个我就叫它双层指针,第二个叫行指针。有人喜欢把第一个叫二维指针吧,如果这么叫很容易晕。

    实际上前者是一个指向指针的指针,而后者是一个带长度的指针。前者可以指向后者。后者与一般指针的差别就是你p++的时候是移动多个变量长度!其他指针只移动一个变量长度。这里的长度说的是存储变量地址的大小。前者指向一个指针变量的地址,而后者多用于指向一个二维数组的地址。所以传递二维数组参数的时候,用后者!

    46.    关于%模运算

    其实是可以模负数的!但和数学定义不同,对于负数的模仍然带有符号。

     

     

  • 相关阅读:
    UVALive2678子序列
    UVA11549计算机谜题
    UVA11520填充正方形
    LA3635派
    UVALive3971组装电脑
    记录未完成题目
    SPOJ 6219 Edit distance字符串间编辑距离
    ACM组队安排-——杭电校赛(递推)
    逆袭指数-——杭电校赛(dfs)
    油菜花王国——杭电校赛(并查集)
  • 原文地址:https://www.cnblogs.com/wohenben/p/5398358.html
Copyright © 2020-2023  润新知