C语言内存分为五个区:
1、栈:用来存放函数的形参和函数内的局部变量。由编译器分配空间,在函数执行完后由编译器自动释放。
2、堆:用来存放由动态分配函数(如malloc)分配的空间。是由程序员自己手动分配的,并且必须由程序员使用free释放。如果忘记用free释放,会导致所分配的空间一直占着不放,导致内存泄露。
3、全局局:用来存放全局变量和静态变量。存在于程序的整个运行期间,是由编译器分配和释放的。
4、文字常量区:例如char *c = “123456”;则”123456”为文字常量,存放于文字常量区。也由编译器控制分配和释放,不允许修改。
5、程序代码区:用来存放程序的二进制代码。
全局变量、静态全局变量、静态局部变量 全局区
函数参数、局部变量 运行时堆栈
字符串常量 文字常量区
Const:
Int const *pci; 指针所指向的值不能被修改,但是指针的值可以修改
Int * const cpi; 指针的值不可以修改,但是指针所指向的值能被修改
Int const * const cpci; 指针本身和所指向的值都不能被修改
const在c++偏向于“常量”,而在c中偏向于“只读”,也可以通过指针方式修改(vc中waring)
c++ :array[const] 正确
c :array[const] 错误
在c++中,编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
在c中,const是一个不能被改变的普通变量,既然是变量,就要占用存储空间,所以编译器不知道编译时的值
符号表:
(1)是吧编译程序保持(记录)源程序中各种名字的属性和特征信息的各种表
(2)符号表中信息在编译各阶段都要使用,编译结束生产目标代码后,符号表中信息也随着删除
(3)编译结束时,符号表中的信息体现在目标代码的存储单元的地址
如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针,例如函数
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();
静态变量将初始化为0
自动变量无缺省初始化值,自动变量的初始化较之赋值语句效率并无提高。除了声明为const变量之外,在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别。除非对自动变量进行显示的初始化,否则当自动变量创建时,它们的值总是垃圾
逗号表达式:这些表达式自左向右逐个求值,整个逗号表达式的值就是最后哪个表达式的值
像如下程序可考虑用逗号表达式:
a = get_value();
count_value(a);
while(a > 0)
{
...
a = get_value();
count_value(a);
}
使用逗号表达式:
while(a = get_value(), count_value(a), a > 0)
{ ... }
零是假,任何非零值皆为真
C中不能一块比较,15 <= a <= 10从左向右运算,15 <= a为真是表达式为1,接着再比较1<=10,
若为真,此表达式的值为1,输出:
In range
int a = 20;
if(1 <= a <= 10)
printf("In range/n");
else
printf("Out of range/n");
前缀++:先增值,再拷贝,返回拷贝
后缀++:先拷贝,再增值,返回拷贝
总:++操作符都是对其拷贝的操作
*p++:
++的优先级高于*,但是,事实上,这里涉及三个步骤:
(1)++操作符产生cp的一份拷贝
(2)然后++操作符增加cp的值
(3)最后,在cp的拷贝上执行间接访问操作(通过指针访问它所指向的地址的过程)
试分析:
int a = 3;
int *p = &a; //设p = 2534
*p++ 3 p = 2538
(*p)++ 3 p = 2534
*++p ? p = 2538
++*p 4 p = 2534
++*++p ? p = 2538(gcc中有问题,应该是gcc的问题)
++*p++ 4 p = 2538
*++p++ 左值无效,不能对拷贝在拷贝,无意义
Printf中参数的输出顺序因编译器不同而异,gcc编译器可能会出现预想不到的结果,属编译器有bug
printf("%d %d %d %d/n", pa++, pa++, pa++ , pa++); //在VC6.0中输出四个相同的数,在gcc中四个数不同
printf("%d %d %d %d/n", ++pa, ++pa, ++pa , ++pa); //在VC6.0中输出不同的四个数,在gcc中四个数相同
#include<stdio.h>
void reverse_string(char *string)
{
char temp, *pend, *pt;
printf("%s/n", string);
for(pt = string; *pt != '/0'; pt++);
pt--;
pend = pt;
pt = string;
for(; pt < pend; pend--, pt++)
{
temp = *pt; //不能char *temp; *temp = *pt; 因为temp所指向的单元是不可预见的
*pt = *pend;
*pend = temp;
}
printf("%s/n", string);
}
int main()
{
//char *dyp = "dingyuanpu"; //字符串常量存放在只读存储区域
//参照:http://blog.csdn.net/dingyuanpu/archive/2010/07/24/5760766.aspx
char dyp[] = "dingyuanpu"; //而改成这样,对应一个数组,数组在栈上,可写
reverse_string(dyp);
return 0;
}
指针比下标更有效率的场合:
当你在数组中1次1步(或某个固定的数字)地移动时,与固定数字相乘的运算在编译时完成,所以在运行时的指令就少一些。在绝大多数的机器上,程序会更小一些、更快一些
如:
Int array[10], a;
For(a = 0; a < 10; a+= 1)
Array[a] = 0;
Int array[10], *ap;
For(ap = array; ap < array + 10; ap++)
*ap = 0;
指针和下标效率完全相同的场合:
A可能是任何值,在运行时方知。所以两种方案都需要乘法指令,用于对a的值就行调整。
如:
A = get_value();
Array[a] = 0;
A = get_value();
*(array + a) = 0;
寄存器变量:
设置寄存器变量的目的是为了提高对有关变量的存取速度,存取寄存器的速度要比存取内存单元快得多。如,一个循环语句的控制变量可以声明为寄存器变量,一般变量不能说明为寄存器变量。
只有局部变量和形式参数可以说明为寄存器变量,全局变量不能说明成寄存器变量,即在函数外的说明中不能使用register修饰符。
寄存器的地址是不可存取的,因此不管一个寄存器变量实际上是否分配在寄存器中,都不能在程序中使用它们的地址,从而不能让指针指向寄存器变量。
如:register char C;
char *cp=&C;(错)
只有局部自动变量和形式参数才能够被定义为寄存器变量,全局变量和局部静态变量都不能被定义为寄存器变量
因为全局变量和局部静态变量都被放在静态存储区中,而寄存器变量被放在寄存器中,一个变量当然只能选择两种存放方式中的一种。
其次,一个计算机系统中的寄存器数量是有限的,因此不能定义任意多个寄存器变量。而且对于不同的系统来说,所允许使用的最大寄存器数量也是不同的
指向二维数组指针的声明:
int array[3][10] , (*p)[10];
p并不是指向指针的指针
做形参:
Void func(int p[3] [10]);
Void func(int p[ ] [10]);
Viod func(int (*p)[10]);
Strlen函数用于计算一个字符串的长度,它的返回值是一个无符号整数,所以它用于表达式时应该小心
如:
If(strlen(x) >= strlen(y)) 按照预想的工作
If(strlen(x) - strlen(y) >= 0) 永远为真
位段:c特有的数据结构,它允许我们定义一个由位组成的段,并可为它赋以一个名字
详见:http://blog.csdn.net/dingyuanpu/archive/2010/07/26/5767117.aspx
struct packed_struct
{
unsigned int f1 :1;
unsigned int f2 :1;
unsigned int f3 :1;
unsigned int type :4;
unsigned int index :9;
};
讨论一下技巧,怎样省略双链表中的根节点的值字段?
答:如果根节点是动态分配内存的,我们可以通过只为节点的一部分分配内存来达到目的。
Node *root;
root = malloc(sizeof(Node) – sizeof(ValueType));
一种更安全的方法是声明一个只包含指针的结构。指针就是这类结构之一,每个节点只包含这类结构中的一个。这种方法的有趣之处在于结构之间的相互依赖,每个结构都包含了一个对方类型的字段。这种相互依赖性就在声明它们是产生了一个“先有鸡还是先有蛋”的问题:哪个结构先声明呢?这个问题只是通过其中一个结构标签的不完整声明来解决。
struct DLL_NODE; //不完整声明
struct DLL_POINTERS
{
struct DLL_NODE *fwd;
struct DLL_NODE *bwd;
};
struct DLL_NODE
{
struct DLL_POINTERS *pointers;
int value;
};
函数指针:
Int f(int);
Int (*pf)(int) = &f;
回调函数:通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就是这事回调函数。回调函数不是有该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对事件或条件进行相应
宏与函数:
宏非常频繁地用于执行简单的计算,为什么不用函数完成这个任务呢?
1、 用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面更胜一筹
2、 更为重要的是,函数的参数必须声明为一种特定的类型,而宏是与类型无关的
3、 还有一些任务无法用函数实现,如:
#define MALLOC(n, type) /
((type *)malloc((n) * sizeof(type)))
不利之处:
每次使用宏时,一份宏定义代码的拷贝都将插入到程序中。除非宏非常短,否则宏可能大幅度增加程序的长度
Char pstr[] = “dingyuanpu”;
Sizeof(pstr) = 11; //包含’/0’
Strlen(pstr) = 10; //不包含’/0’
Char *pstr = “dingyuanpu”;
Sizeof(pstr) = 4;
Strlen(pstr) = 10; //不包含’/0’
Main函数的第一个参数*argv是该文件的名字,第二个参数才是我们想要的吧
IO函数以三种基本的形式处理数据:单个字符、文本行、二进制数据
fsrc = fopen("D:/c/static/dingyuanpu.txt", "r"); //注意’/’的方向
文件操作函数:
FILE *fopen(char const *name, char const *mode);
FILE *freopen(char const *filename, char const *mode, FILE *stream);
int fclose(FILE *f);
int getchar(void); == getc(stdin) //宏
int putchar(int character); //宏
int getc(FILE *stream); //宏
int putc(int character, FILE *stream); //宏
int fgetc(FILE *stream);
int fputc(int character, FILE *stream);
int ungetc(int character, FILE *stream);
char *gets(char *buffer); //不安全
int puts(char const *buffer);
char *fgets(char *buffer, int buffer_size, FILE *stream); //安全
int fputs(char const *buffer, FILE *stream);
int fscanf(FIlE *stream, char const *format, ...);
int fprintf(FILE *stream, char const *format, ...);
int sscanf(char const *string, char const *format, ...);
int sprintf(char *buffer, char const *format, ...);
二进制形式写入,是吧数据写到文件效率最高的方法。因为二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度的损失
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
int fflust(FILE *stream);
int fseek(FILE *stream, long offset, int from); // from:SEEK_SET,SEEK_CUR,SEEK_END
int ftell(FILE *stream); //随即访问,返回流的当前位置
int fgetpos(FILE *steam, fpos_t *position); //ftell的替代方案
int fsetpos(FILE *stream, fpos_t const *position); //fseek的替代方案
void rewind(FILE *stream); //回指流起始位置
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
int feof(FILE *stream);
int ferror(FILE *stream);
clearerr(FILE *stream);
FILE *tmpfile(void);
char *tmpnam(char *name);
int remove(char const *filename);
int rename(char const *oldname, char const *newname);
setjmp 是 C 语言解决 exception 的标准方案。我个人认为,setjmp/longjmp 这组 api 的名字没有取好,导致了许多误解。名字体现的是其行为:跳转,却没能反映其功能:exception 的抛出和捕获。
int setjmp(jmp_buf jmpb) 设置缓冲区来保存堆栈的内容,将保存的上下文存入进程的自身的数据空间(u区),并继续在当前的上下文中执行,一旦碰到了longjmp,进程就从该进程的u区,取出先前保存的上下文,并恢复该进程的上下文为先前保存的上下文。这时核心将使得进程从setjmp处执行(摘自:unix平台下c语言高级编程 指南)
对于返回char *的函数,只能用char *p的方式接收,因为数组名不能改变执行方向
P = char *f();
平衡二叉树,又称AVL树。它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。
Sizeof()的返回类型是无符号数,if语句在signed int和unsigned int之间测试相等性,signed将被升级为unsigned。尽量不要在你的代码中使用无符号数,以免增加不必要的复杂性,不要仅仅因为无符号数不存在负值(如年龄)而用它来表示数量。只有在使用位段和二进制掩码时才可以使用无符号数
NUL:结束一个ANSII字符串
NULL:什么也不指向(空指针)
所有的赋值符(包括复合赋值符)都具有右结合性
char * const *(*next) ( )
void ( *signal ( int sig, void ( *handler ) ( int ) ) ) ( int );
size a.out :显示该文件的段分布
程序1:
程序2:
int ar[300000] = {1, 2, 3, 4, 5, 6 };
void main()
{}
发现程序2编译之后所得的.exe文件比程序1的要大得多。当下甚为不解,于是手工编译了一下,并使用了/FAs编译选项来查看了一下其各自的.asm,发现在程序1.asm中ar的定义如下:
_BSS SEGMENT
?ar@@3PAHA DD 0493e0H DUP (?) ; ar
_BSS ENDS
而在程序2.asm中,ar被定义为:
_DATA SEGMENT
?ar@@3PAHA DD 01H ; ar
DD 02H
DD 03H
ORG $+1199988
_DATA ENDS
区别很明显,一个位于.bss段,而另一个位于.data段,两者的区别在于:全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。.bss是不占用文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
printf(" %d /n", sizeof('a')); 输出:4(在c++中是1)
char chr = 'a';
printf(" %d /n", sizeof(chr)); 输出:1
printf(“%d /n”, sizeof(char)); 输出:1
字符常量的类型是int(提升规则),字符变量的类型是char。
在进行运算时,不同类型的数据要先转换成同一种类型,然后再进行运算
字符必定先转换int
Float必定转换成double,以提高运算精度(即使是两个float类型的相加,也先转换成double)
Int和double相加是,int先转换成double
1、检验一个链表中是否存在循环
算法:指针追赶
2、库函数调用和系统调用的区别何在?
库函数:调用函数库中的一个程序,与用户程序相联系,在用户地址空间内执行,运行时间属于“用户”时间,属于过程调用,开销较小
系统调用:调用系统内核的服务,是操作系统的一个进入点,在内核空间内执行,运行时间属于“系统”时间,需要在切换到内核上下文环境然后切换回来,开销较大
系统调用比库函数调用慢很多,因为它需要把上下文环境切换到内核模式。纯粹从性能上考虑,应该尽可能减少系统调用的数量。但是许多C库函数中的程序通过系统调用开实现功能,比如:int system(char *command);
4、文件描述符与文件指针有何不同?
FILE指针指向一个流结构,它在stdio.h中定义。结构的内容根据不同的编译器有所不同,在UNIX中通常是开放文件的每个进程的一个条目。在典型情况下,它包含了流缓冲区、所有用于提示缓冲区中有多少字节是实际的文件数据的变量,以及提示流状态的标志(如ERROR和EOF)等
所以,文件描述符就是开放文件的每个进程表的一个偏移量(如“3”)。它用于UNIX系统调用中,用于标识文件
FILE指针保存了一个FILE结构的地址,FILE用于表示开放的I/O流(如hex20938)。它用于ANSI C标准IO库调用中,用于标识文件
C库函数fdopen()可以用于创建一个新的FILE结构,并把它与一个确定的文件描述符相关联(可以有效的在文件描述符小整数和对应的流指针之间进行转换,虽然它并不在开放文件表中产生一个额外的新条目)
5、编写一些代码,确定一个变量时有符号数还是无符号数
无符号数的本质特征是它永远不会是负的,有符号数的本质特征是对最左边的一位取反将会改变符号
#include <stdio.h>
#define IS_UNSIGNED1(a) (a >= 0 && ~a >= 0)
#define IS_UNSIGNED2(type) ((type)0 - 1 > 0)
void main()
{
int b = 0;
printf("%d/n", IS_UNSIGNED1(b));
printf("%d/n", IS_UNSIGNED2(unsigned int));
}