Chinaunix首页 | 论坛 | 博客
  • 博客访问: 278992
  • 博文数量: 91
  • 博客积分: 1772
  • 博客等级: 上尉
  • 技术积分: 930
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-17 19:49
文章分类

全部博文(91)

文章存档

2012年(1)

2011年(36)

2010年(50)

2008年(4)

我的朋友

分类: LINUX

2010-04-27 23:25:14

c语言核心技术 一收藏

================================================

C语言编译分析记号,有一个原则是尽可能靠左合并符号使其得到符合语法的记号,所以
a+++b 会被解释为 (a++)+b 而不是 a+(++b)

================================================

函数作用域和语句块作用域:
一直以为函数内部申明的标识符其作用域就叫做函数作用域,其实应该是语句块作用域,它和函数作用域还是有区别的,语句块作用域的作用范围是从申明处开始,到包含这个申明的最小语句块结束为止。即使你申明在函数的最开头,他依然是属于语句块作用域,什么样的标识符具有函数作用域呢? 嘿嘿,goto用的Label

这个从目标代码的角度来看就很明显了,标识符这些基本上是放在寄存器或堆栈内,没有赋值之前自然没法使用,Label直接就翻译成一个地址,写在jump命令里,当然在函数内部哪都可以用了。

================================================

类似 x+=y之类的复合赋值运算表达式和 x=x+y这样的表达式效果上有什么区别么?区别在于x+=y这种形式里,左值x只被计算了一次,如果x是一个有副作用的表达式,着两种表达方式可能就不等价了。

逗号运算符的表达式操作顺序是从左到右,所以整个运算结果是最后一个表达式的结果,计算的顺序可以得到保证。 但是在函数调用时,函数的参数列表里面的逗号“并不是”逗号运算符,所以函数调用时,参数表达式的计算顺序C语法是没有保证的。

================================================

元素修饰符:
类似初始化结构体时,C99里面可以用成员修饰符(类似 .member = value 的形式)来初始化特定的结构元素一样,数组的初始化可以用:
int A[30] = {1, 2, [15] = 15, 16 };
这样的形式来初始化第1,2,15,16个元素。

================================================

对数组采用指针来操作通常可能比采用下标索引来操作要更有效率,按数组操作可能涉及到计算索引再加基地址的问题,指针本身就可以递增。

这个要看下标索引的计算复杂度,如果直接就是类似循环变量i这样,其实下标表示还更快

测试一下:

int main()
{
        int a[40];
        int *pa=&a[0];
        int i,j;

        for(i=0; i<10; i++)
                a[i] = 3;

        for(i=0; i<10; i++){
                *pa++ = 4;
        }
        return 0;
}

循环部分汇编代码,指针版还要多一条命令:

------


.L3:
        movl    -12(%ebp), %eax # i, i.0
        movl    $3, -176(%ebp,%eax,4)   #, a
        addl    $1, -12(%ebp)   #, i
.L2:
        cmpl    $9, -12(%ebp)   #, i
        jle     .L3     #,


.L6:
        movl    -16(%ebp), %eax # pa, pa
        movl    $4, (%eax)      #,* pa
        addl    $4, -16(%ebp)   #, pa
        addl    $1, -12(%ebp)   #, i
.L5:
        cmpl    $9, -12(%ebp)   #, i
        jle     .L6     #,


如果下标计算比较复杂,指针就要更快了


int main()
{
        int a[40];
        int *pa=&a[0];
        int i,j;

        for(i=0; i<10; i++)
                a[3*i] = 3;

        for(i=0; i<10; i++){
                *pa = 4;
                pa += 3;
        }

        return 0;
}

循环部分汇编代码,指针版要少两条命令:
--------------

.L3:
        movl    -12(%ebp), %eax # i, i
        movl    %eax, %edx      # i, i
        addl    %edx, %edx      # i
        leal    (%edx,%eax), %eax       #, D.1786
        movl    $3, -176(%ebp,%eax,4)   #, a
        addl    $1, -12(%ebp)   #, i
.L2:
        cmpl    $9, -12(%ebp)   #, i
        jle     .L3     #,
        movl    $0, -12(%ebp)   #, i
        jmp     .L5     #


.L6:
        movl    -16(%ebp), %eax # pa, pa
        movl    $4, (%eax)      #,* pa
        addl    $12, -16(%ebp)  #, pa
        addl    $1, -12(%ebp)   #, i
.L5:
        cmpl    $9, -12(%ebp)   #, i
        jle     .L6     #,

 

================================================

弹性结构成员

C99允许结构体的最后一个成员属于不完整的数组类型,如:
typedef Struct { int len; int data[]} A_t;
分配内存的时候,要用类似
malloc( sizeof(A_t) + 10*sizeof(int) );
的方式来为data数组分配内存。

这种情况下,sizeof计算 A_t 的size的时候,这个弹性结构成员的大小是不会被计算的。
--- to do --- 写一个小代码验证一下

通常如非特殊必要,用指针的方式来实现结构体内的空间可变数据项,要比使用这种弹性结构成员更合适。

================================================

 

c语言核心技术 二收藏
作者:刘旭晖 colorant@163.com转载请注明出处
http://blog.csdn.net/colorant/

================================================

malloc 和 calloc

calloc会把分配出来的内存中的每一个字节都初始化为0, 这可以保证不仅是将分配给结构对象的内存都初

始化为0,包括成员之间的Padding也是0

此外,个人觉得用Calloc的好处在于他能保证你分配出来的内存确实可用。因为malloc实际上只是预留出一

部分内存地址,在真正使用之前并不真正分配内存。 这可能导致你可以成功的分配很大的内存,结果发现

最后使用的时候OOM Out of Memory了。 有篇很好的文章详细的分析了这一问题:

http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html

================================================

fopen时的访问模式 r 和 w

很少在应用层编程,一直以为r就是读,w就是写,不能混用。 原来这不是最根本的区别,r+ 和 w+ 就是读

写都可以的。最更本的区别在于,如果一个文件不存在,r会失败,w会创建。如果一个文件已经存在 r模式

打开,w模式会打开并truncate(清空)

================================================

#include 头文件的嵌套层数限制

书上说是16层,也就是说一个头文件包含另一个头文件,不断的包含最多可以有16层的深度。
觉得少了一点,试了一下,用递归嵌套 1.h 包含2.h 2.h包含1.h

x86 上 gcc4.1.2 出错提示 #include nested too deeply 的时候,嵌套了198层,这个。。。真的足够用

了。


================================================

printf输出格式中,可以用变量来指定字段宽度,在位宽描述符的地方使用*,对应变量位置即可:

        char str[]="hello , guys";
        int width = 20;

        printf("%*s\n",width,str);

================================================

GCC:

想要得到GCC预处理一个文件的结果(插入头文件,展开宏),可以使用 -E 参数:

GCC -E -o file.i file.c

-C 参数可以保留源文件中的注释


用GCC输出汇编语言代码时,可以使用 -fverbose-asm 将C语言中的变量名作为汇编语言的注释。


逐行对比查看GCC如何把C语言翻译成汇编代码,这个应该挺有用:

gcc -g -o file.o -Wa,-a=file.asm,-L file.c

================================================

Makefile 中 =  :=  ?= 的区别:

= 递归展开,变量会原封不动的存储字符序列,知道被引用的时候再展开。
:= 简单展开,在变量赋值的时候就展开,后续修改对它赋值时引用的变量不影响它的值。
?= 如果变量原先没有赋值,则递归展开,否则不变。 这个应该比较常出现在判断变量是否已经在环境变量中被定义的情况。

如下Makefile

STR = "INIT"
STR2 = "INIT"
STR3 = "INIT"

NAME = "First"
STR = $(NAME)
STR2 := $(NAME)
STR3 ?= $(NAME)
STR4 ?= $(NAME)

NAME = "Second"

ALL:
        @echo $(STR)
        @echo $(STR2)
        @echo $(STR3)
        @echo $(STR4)

Make的输出结果是:

Second
First
INIT
Second

 
阅读(802) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~