从.C文件到可执行文件具体流程
具体流程:
1、预处理:把源文件翻译成预处理文件
gcc -E code.c 显示与处理结果
gcc -E code.c -o code.i 生成以.i 结尾的预处理的文件
2、编译:把预处理文件翻译成汇编文件
gcc -S code.i 默认生成以.s 结尾的汇编文件
3、汇编:把汇编文件翻译成二进制的目标文件
gcc -c code.s 默认生成以.o 结尾的目标文件
4、链接:把若干个目标文件合并成一个可执行文件
gcc code.c a.o b.o c.o ... 默认生成一个a.out 的可执行文件
指针的作用
1、共享变量 2、提高传参效率 3、记录堆内存地址 4、与字符串结合
写指针函数
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
… …
static,extern
static:
限制全局变量和函数的作用域。
改变局部变量、块变量的储存位置:由stack改为data或bss。
延长局部变量、块变量的生命周期:不会被销毁,初始化语句仅第一次有效。
extern:
用于声明外部的变量,当a.c中有一个全局变量,b.c想要使用该变量就得用 extern 声明一下来使用。
extern声明只能让编译通过,如果链接时找不到,依然会报错。
有哪些类型限定符
signed、unsigned、extern、auto、typedef、const、static、volatile、register
内存管理
一个C语言程序而言,内存空间主要由五个部分组成代码段(text)、数据段(data)、静态数据段(bss),
堆(heap)和栈(stack)组成,
其中代码段,数据段和静态数据段是编译的时候由编译器分配的,而堆和栈是程序运行的时候由系统分配的。
堆和栈的区别
堆是进程中的一个内存段,特点是足够大;栈是一种功能受限的表,只有一个出入端口,先进后出。
堆和栈的区别:
1、 栈中内存是由OS自动申请和自动释放; 堆中的内存需要程序员手动申请与释放。
2、 栈的空间大小并不大,一般最多为2M,超过之后会报Overflow错误。堆的空间非常大,最大可到达4G,可操作的空间非常大。
3、 栈由于其先进后出的特性,不会产生内存碎片; 堆中频繁调用malloc和free,会产生内存碎片。
4、 栈是由高地址向低地址扩展,自上而下; 堆是由低地址向高地址扩展,自下而上。
5、 栈有计算机专门分配的寄存器存放地址,有着很高的效率;堆需要通过库函数进行分配并在内存中搜寻足够大的空间,效率较低。
(堆都是动态分配的,没有静态分配。但是栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。
动态分配由malloc函数实现,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行和释放,无需程序员进行操作。)
全局变量,局部变量,函数传参 存在哪
被初始化过的全局变量存在data数据段,未被初始化过的全局变量存在bss静态数据段。
局部变量存储在stack栈中,但被初始化过的静态局部变量存储在data数据段,
未被初始化过的静态局部变量存储在bss静态数据段。
函数传参存储在栈中。
判断大小端(写出函数),为什么
地址由低位到高位编号,数据由高位到低位编号。
大端系统是数据的高字节存储在内存的低位地址;
小端系统是数据的高字节存储在内存的高位地址。
方法一:直接法
int main()
{
int a = 0x12345678;
char i = a;
printf("%x",i);
return 0;
}
定义一个十六进制int型数据0x12345678、一个char类型数据。
因为int型数据大小为4字节,而char类型数据为1个字节。
所以将int型数据赋值给char时会丢失3个字节数据,char类型中存储的是int类型中低地址的数据。
将char类型获取的数据输出后,如果输出的是12则为大端存储,如果输出的是78则为小端存储。
方法二:指针法
int main()
{
int x = ox12345678;
char *c = (char*)&x;
if(c[0] == 0x12
printf("big\n");
else
printf("little\n");
return 0;
}
将0x12345678保存到int变量x中,在将其转换位char数组。如果数组第一个值是0x12那就是大端的,
而如果是0x78则说明是小端的了。
方法三:联合体法
union A
{
char a;
int b;
}A;
int main()
{
A.b = 0x12345678;
if(A.a == 0x12)
printf("big\n");
else
printf("little\n");
return 0;
}
在union中所有的数据成员共用一个空间,而且是从低位开始占用,所有的数据成员具有相同的起始地址。
在联合体中定义两个不同类型的变量,并定义第2个变量的地址为0x12345678。
如果第一个变量的地址为0x12则为大端,否则为小端。
0X12345678 大小端转换
移位法:
int main()
{
int a = 0x12345678,b;
b = ((a&0x000000ff)<<24) | ((a&0x0000ff00)<<8) | ((a&0x00ff0000)>>8) | ((a&0xff000000)>>24);
printf("%x\n",b);
return 0;
}
sizeof和 strlen 计算字符串,指针, 数组
sizeof对于字符串的处理与指针相同(4/8);而数组是根据数组定义时申请的内存空间大小(未定义申请大小时'\0'也算)。
strlen对字符串的处理和数组相同(以实际字符个数计算;'\0'不算);而对于未赋值的指针是一个不确定的值,
对于赋值的指针是按照实际的字符个数计算。
默写memcpy
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:从src位置中拷贝n个字节到dest中
Memcpy以及如果地址重叠了该如何拷贝
void *memcpy(void *dest, const void *src, size_t n);
功能:从src位置中拷贝n个字节到dest中
地址重叠的话,使用与memmove:
mommove与memcpy相同,不同的是当to和from重叠,行数仍然可以工作。
#include <string.h>
void *memmove( void *to, const void *from, size_t count );
const和#define的区别
编译器处理不同:
宏定义是一个“编译时”概念。在预处理阶段展开(在编译时进行替换),不能对宏定义进行调试,生命周期结束于编译时期;
Const是在编译、运行时起作用。
存储方式不同:
宏定义直接替换,不会分配内存,存储于程序的代码段中;
Const常量需要进行内存分配,占用数据段空间。
类型和安全检查不同:
宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生错误;
Const定义是常量的声明,有类型区别,需要在编译阶段进行类型检查。
定义后能否取消:
宏定义可以通过#undef来使之前的宏定义失效;
Const常量定义后将在定义域内永久有效。
Makefile的内容,简单写一个makefile
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
OBJ=main.o phone.o tools.o
all:$(OBJ)
gcc -o phone_book $(OBJ)
main.o:main.c tools.h phone.h
gcc -c main.c
tools.o:tools.c tools.h
gcc -c tools.c
phone.o:phone.c phone.h tooh.h
gcc -c phone.c
clean:
rm -rf $(OBJ)