1.寄存器
寄存器是CPU内部用来暂时存放数据/地址/指令的小型存储区域
寄存器距离ALU最近,存储速度最快
数据从内存里拿来先放到寄存器,然后CPU再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU不直接与内存打交道
registe变量必须是能被CPU寄存器所接受的类型,这意味着register变量必须是一个单个的值,并且其长度应小于等于整型长度,而且register变量可能不存放在内存中,所以不能用取地址操作符&来获取register变量的地址
2.优先级问题
.的优先级高于*
p.f - 对p取f偏移,作为指针,然后进行解引用操作 (p.f) 可用->来解决这一问题
[]的优先级高于
int ap[] - ap是一个指向int数组的指针
函数()高于
intfp() fp是个函数,返回int*
==和!=操作高于位运算
(val&mask!=0)
val&(mask!=0)
==和!=高于赋值符
c=getchar()!=EOF
c=(getchar()!=EOF)
算术运算符高于位移运算符
msb<<4+lsb
msb<<(4+lsb)
逗号运算符在所有运算符中优先级最低
i=1,2
(i=1),2
3.const
编译器通常不为const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高
#define M 3
const int N=5;//此时并未将N放入内存中
int i=N;//此时为N分配内存,以后不再分配内存
int I=M;//预编译期间进行宏替换,分配内存
int j=N;//没有内存分配
int J=M;//再进行宏替换,又一次分配内存
const定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的立即数,所以,const定义的只读变量在程序运行过程中只有一份备份(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个部分。#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译期间确定其值。#define宏没有类型,而const修饰的只读变量具有特定的类型
4.无法向函数传递一个数组
在C语言中,所有非数组形式的数据实参均以传值形式(对实参做一份备份并传递给它的那份备份)调用,然而,如果要复制整个数组开销将会非常大,函数的返回值也不能是函数,也只能是指针。函数本身是没有类型的,函数的返回值才有类型
5.((void()()0)()
①.void()()是函数指针,该函数指针指向一个函数,该函数的返回值为void,参数为空
②.(void()())0,将0强制转换为函数指针类型,0是一个地址,也就是将一个函数保存在首地址为0的一个区域内
③. ((void()())0) ,这是取0地址开始的一段内存的内容,其内容就是保存在首地址为0的一段区域内的函数
④.((void()())0)()这就是函数调用
6.offsetof宏的实现
#define offsetof(type,name) (size_t)&(((type*)0)->name)
Type是结构体类型名,Name是成员名。具体操作方法是:
1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。
2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。
3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量。
7.写代码向内存0x12ff7c地址上存入一个整型数0x100
int*p=(int*)0x12ff7c
*p=0x100;
或
*(int*)(0x12ff7c)
8.volatile
volatile与const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素修改,比如操作系统、硬件、或者其他线程,遇到这个关键字声明的变量,编译器就不再对其进行优化,从而可以提供对特殊地址更稳定的访问
int i=10;
int j=i;
int k=i;
volatile int i=10;
int j=i;
int k=i;
代码1这时候编译器对代码进行优化,因为在代码1的语句中,i没有被用作左值(没有被赋值),这时候编译器认为i的值没有发生改变,所以在第一条语句从内存中取出i的值赋值给j后,这个值并没有被丢掉,而是在第二条语句继续给k赋值。编译器不会生成出汇编代码重新从内存中取i的值(不会编译生成装载内存的指令,比如ARM的LDM指令),提高效率,但要注意,两条语句之间i没有用作左值(没有被赋值)才行
代码2中,volatile告诉编译器,i是随时可能发生变化的,每次使用它时必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i的地址读取数据放到k内
9.关于static
static int j;
int k=0;
void fun1(void){
static int i=0;
i++;
}
void fun2(void){
j=0;
j++;
}
int main(){
for(k=0;k<10;k++){
fun1();
fun2();
}
}
}
由于被static修饰的局部变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会销毁,函数下次使用时仍然能用到这个值,所以,i的值只被初始化一次,而j是全局变量,每次调用函数都初始化
静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效
10.模拟实现atoi
//atoi 将一个字符串转化为一个整型
#include<stdio.h>
#include<assert.h>
#include<limits.h>
#include<ctype.h>
enum state
{
Value,
ErrValue
};
state statue = ErrValue;
int my_atoi(const char*str)
{
assert(str);
char* p = (char*)str;
long long n = 0;
int flag = 1;
//字符串长度为0
if (*str == '\0')
{
return 0;
}
//字符串前面有很多空格
while (*p == ' ')
{
p++;
}
//判断符号
if (*p == '+')
{
flag = 1;
p++;
}
else if (*p == '-')
{
flag = -1;
p++;
}
//开始计算
while (*p!='\0')
{
if (isdigit(*p))
{
n = n * 10 + flag * (*p - '0');
if (n >INT_MAX)
{
n = INT_MAX;
break;
}
else if (n < INT_MIN)
{
n = INT_MIN;
break;
}
}
else
{
break;
}
p++;
}
if (*p == '\0')
{
statue = Value;
}
return (int)n;
}/**
特殊情况
1前面有一大堆空格
2传过来空指针
3字符串长度为0
4整形溢出
5含有其它字符
*/
int main()
{
char arr[] = "12345678";
int m = my_atoi(arr);
if (statue == Value)
{
printf("%d\n", m);
}
if (statue == ErrValue)
{
printf("非法转化\n");
}
return 0;
}