Deep analysis in C - HsuJv/Note GitHub Wiki
- 定义是创建一个数据对象, 并且 分配内存
- 声明是告诉编译器, 有这么一个类型的符号, 在编译时如此处理, 等待链接的时候再关联符号的具体地址
- 声明与定义最主要的区别就是是否分配了内存
- auto:
- 与c++11的auto不同, c里面, auto关键字用于指定变量的存储类型, 说明该变量应该存放于栈空间, 并在函数退出时被删除
- auto是缺省的
- register:
- 请求编译器 尽可能 将变量存在寄存器中而不是内存里
- register修饰的变量应该是一个单个值, 并且大小小于寄存器位数
- 使用register修饰的变量无法取地址(&)
- static:
- 既能修饰变量又能修饰函数
- 被static修饰的符号作用域缩小至其所在的最小作用域(e.g.函数内的变量作用域仅在当前函数, 函数外的变量作用域仅在当前文件, 修饰函数时同理)
- 被static修饰的变量是存储在堆区的, 不会因为函数的退出而销毁
- 被static修饰的符号在符号表中会以另一个名字出现(e.g.
static int a;
变量a可能被存储为 a@1), 这就允许了非static符号与static符号同名
- void 用于:
- 限定函数的返回
- 限定函数的参数
- 定义/声明一个泛型指针(void *)
- 修饰函数返回:
- 未明确声明函数返回值的, 编译器一般认为该函数返回一个 int
- 实验代码
#include <stdio.h> fun(){ asm("movq $1, %rax"); } int main(){ printf("%d\n", fun()); }
- 运行后发现输出1 (gcc 8.2.0)
- 修饰函数参数:
- 未明确声明函数参数的, 编译器允许传递任意参数的调用
- 实验代码:
#include <stdio.h> long fun(){ asm("xorq %rdi, %rax"); } int main(){ printf("%ld\n", fun(4, 2)); }
- 运行后发现输出4 (gcc 8.2.0)
- 当明确指定函数接受void型参数之后, 这样的调用编译器不给通过
- void指针
- 为了能表示一个通用的指针变量, c引入了void *类型
- void指针能指向任何类型的指针
- ANSI标准中, 对void指针的算法操作是不合法的, 而GNU标准中, 会将其当成char * 进行计算
- const修饰只读变量
- C语言中, 你无法使用一个const变量紧接着去定义一个数组, 因为他对于编译器来说仍是变量, 而C语言无法用变量定义一个全局数组大小(存储在栈空间的数组用大小用变量定义是合法的)
- 注: c++中, 编译器不会为const的内建(built-in)变量分配内存, 需要使用时会直接去符号表里读取值, 所有可以使用const变量定义全局数组的大小
- const 修饰函数参数:
- 告诉编译器改参数不可改变, 编译器在编译期间会检查函数中是否有修改该参数的动作, 有则报错
- const 修饰的参数同时接受非const和const变量, 当一个非const变量传入时, 会有一个临时变量生成
- 注: c++中, 可以使用const_cast来去除变量的const属性
- volatile告诉编译器, 被修饰的变量应该直接从内存中读取, 也应该直接写入内存, 不应该使用寄存器来优化运行速度
- volatile并不绝对意味着线程安全, 因为语句的执行顺序在优化后不一定是我们所写的顺序
- 空结构体
- 对于一个空结构体, 用关键字sizeof取其大小, 会得到1而不是0
- 柔性数组
- c99中, 允许结构体的最后一个元素是未知大小的数组
- 使用malloc来确定数组大小
- 代码示例:
typedef struct st{ int a; char b[]; }st; st* pst = (st*) malloc (sizeof(st) + 100 * sizeof(char));
- 注意, 我们通过sizeof(st)得到的, 仅仅是其第一个元素a的大小, 即
sizeof(pst->a)
- 如上申请内存后, 就可以通过
pst->b[15]
来访问指定内存了
- struct与class(c++)
- c++中, struct也被实现为class
- 然而, struct的默认成员是public的, 而class是private
- enum 与 define
- define在预编译期间被替换, 而enum则在编译期间确定值
- define一次可以定义一个常量, 而enum可以多个
- define和enum常量均不能取地址
- 方括号, 圆括号, 成员选择
- 单目运算符, sizeof
- 乘除, 取余
- 加减
- 位移
- 比较
- 位运算
- 逻辑
- 三目
- 运算后赋值
-
#error error-message
:- 遇到error指令时, 会显示error-message
-
#line number ["filename"]
- 用于改变编译器所"看见"的当前行数和文件名
-
#pragma message("消息")
- 编译器遇到这条指令时会在编译输出窗口输出预定义好的消息
-
#pragma code_seg(["section-name"[,"section-class"]])
- 一般用于驱动开发
-
#pragma once
- 保证该文件只被一次include, 防止重复包含
- 可移植性不强, 慎用
-
#pragma hdrstop
- 强制结束预编译头文件, 后面的内容不进行预编译
-
#pragma warning(x: number)
:- x可以为disable, once, error, 分别表示禁用某个警告, 某个警告只显示一次, 将某个警告当成error处理
- 也可以使用
#pragma warning(push[, n]/pop)
来保存(n个或全部)/弹出一个警告
-
#pragma comment(..)
- 将一个注释记录放入一个对象文件或可执行文件中
- 通常用法有
-
#pragma comment(lib, "user32.lib")
, 用来将user32.lib加入工程 -
#pragma comment(linker, "some_linker_option")
设置一个链接选项, 以此来代替命令行输入
-
-
#pragma pack(number)
- 用于指定内存对齐
- 当number为空时, 恢复默认对齐方式
- 指针是个变量, 其size与机器字的大小强相关
- 指针变量的地址所存储的值, 是其指向的位置
- 数组名仅是一个符号, 编译器并未为数组名开辟内存存储其位置, 早期的编译器中,
&array
被认为是非法的 - 数组名不能作为左值使用, 因为它没有地址
- 定义为指针, 声明为数组
- 示例代码
----- a.c ----- int * p; ----- a.c ----- ----- b.c ----- extern int p[]; ----- b.c -----
- 链接器能正常链接符号, 但是在使用时, 因为b.c中的p被声明为数组, 编译器会认为p的地址就是这个数组的首地址, 并这个位置开始取值
- 定义为数组, 声明为指针
- 示例代码
----- a.c ----- int a[100]; ----- a.c ----- ----- b.c ----- extern int *a; ----- b.c -----
- 链接器能正常链接符号, 但是在使用时, 因为b.c中的a被声明为指针, 编译器会认为a的值是这个指针指向的值(*a 被解释为 *a[0])