C语言笔记(一)
笑话一枚:
程序员 A:“哥们儿,最近手头紧,借点钱?”
程序员 B:“成啊,要多少?”
程序员 A:“一千行不?”
程序员 B:“咱俩谁跟谁!给你凑个整,1024,拿去吧。”
========================= 我 是 分 割 线 =========================
前言
C语言允许直接访问物理地址,可以直接对硬件进行操作,非常适合开发内核和硬件驱动。
书上看来一句话:普通人用 C 语言在 3 年之下,一般来说,还没掌握 C 语言;
5 年之下,一般来说还没熟悉 C 语言;10 年之下,谈不上精通。
学习一门语言最基本的还是要多码码,多调试,必要的时候可以用小黄鸭调试法。
多思考,遇到问题自己先尝试深入研究和解决。
下面的笔记来自《C语言深度解剖》
变量
定义、声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存。
一般的变量使用驼峰命名法(CamelCase),加上必要的前后缀。
所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
c语言有4种存储类型:auto, extern, register, static,定义变量的时候只能指定其中的一种类型。
而变量分配在内存存储空间的有:BSS区、数据区、栈区、堆区。
进程在内存中的结构
- 代码区:存放CPU执行的机器指令,代码区是可共享,并且是只读的。
- BSS区:存放的是未初始化的全局变量和静态变量。
- 数据区:存放已初始化的全局变量、静态变量(全局和局部)、常量数据。
- 栈区:由编译器自动分配释放,存放函数的参数值、返回值和局部变量,在程序运行过程中实时分配和释放,栈区由操作系统自动管理,无须程序员手动管理。
- 堆区:由malloc()函数分配的内存块,使用free()函数释放内存,堆的申请释放工作由程序员控制,容易产生内存泄漏。
也有变量不在内存中:register 变量可能不存放在内存中,所以不能用取址运算符“&”来获取 register 变量的地址。
static的2个作用
第一个作用:修饰变量。静态全局变量和静态局部变量,都存在内存的静态区。
静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用 extern 声明也没法使用他。
静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。
第二个作用:修饰函数。不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。
好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
sizeof关键字
int i=0;
A),sizeof(int); B),sizeof(i); C),sizeof int; D),sizeof i;
这里只有选项C是错误的,因为 sizeof 在计算变量所占空间大小时,括号可以省略,而计算类型大小时括号不能省略。
还有就是 sizeof 不怕地址越界,它只计算变量类型占空间大小,不会去访问变量的地址,也就不清楚人家存不存在了。
signed、unsigned 关键字
1.下面的代码输出是什么?为什么?
void foo(viod) { unsigned int a = 6; int b = -20; (a+b>6)?puts(">6"):puts("<=6"); }
2.下面的代码的结果是多少?为什么?
int main() { char a[1000]; int i; for(i=0; i<1000; i++) { a[i] = -1-i; } printf("%d",strlen(a)); return 0; }
void 的字面意思是“空类型”,void *则为“空类型指针”,void *可以指向任何类型的数据。
任何类型的指针都可以直接赋值给 void *,无需进行强制类型转换:
void *p1;
int *p2;
p1 = p2;
case 后面的值只能是整型或字符型的常量或常量表达式。
定义 const 只读变量,具有不可变性。const 修饰的仍然是变量,只不过是只读属性罢了,不能当作常量使用。
const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。
const的作用:节省空间,避免不必要的内存分配,同时提高效率
编译器通常不为普通 const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
真时假亦真,假时真亦假
C语言中,0表示逻辑假,非0表示逻辑真。从数值上看,NULL、 '\0' 和 0 是一样的(注意和 '0' 的区别)。C语言不会黑白不分,只要不是假就是真,但是这样会不会太极端吗?
volatile
volatile修饰的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
先看看下面的例子:
inti=10;
intj = i;//(1)语句
intk = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值(没有被赋值)。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。
但要注意:(1)、(2)语句之间 i 没有被用作左值才行。
再看另一个例子:
volatile inti=10;
intj = i;//(3)语句
intk = i;//(4)语句
volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i 的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。
什么时候要使用 volatile ?
如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
大小端模式
3.确认当前系统的存储模式?
int checkSystem{ union check { int i; char ch; }c; c.i = 1; return(c.ch == 1); }
若处理器是 Big_endian 的,则返回 0;若是 Little_endian 的,则返回 1。
大端模式(Big_endian) :字数据的 高字节 存储在 低地址中,而字数据的 低字节 则存放在 高地址中。
小端模式(Little_endian):字数据的 高字节 存储在 高地址中,而字数据的 低字节 则存放在 低地址中。
百度百科:
目前 Intel 的 80x86 系列芯片是唯一还在坚持使用小端的芯片,而 MIPS 和 ARM 等芯片要么采用全部大端的方式储存,要么提供选项支持在大小端之间切换。
另外,对于大小端的处理也和编译器的实现有关,在 C 语言中,默认是小端的(但在一些对于单片机的实现中却是基于大端,比如 Keil C51),Java 是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端。
4.p->i的值为多少?
union { int i; char a[2]; }*p,u; p = &u; p->a[0] = 0x39; p->a[1] = 0x38;
union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始的。
5.在 x86 系统下,以下程序输出的值为多少?
#include <stdio.h> int main(void) { int a[5] = {1,2,3,4,5}; int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int)a + 1); printf("%x,%x\n",ptr1[-1],*ptr2); return 0; }
结构体struct
6.下面2段代码有什么区别?
//代码(1) structTestStruct1 { char c1; short s; char c2; int i; }; //代码(2) structTestStruct2 { char c1; char c2; short s; int i; };
sizeof(TestStruct1)的值为 12
sizeof(TestStruct2)的值为 8
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被 4 整除的地址,和可以被 8 整除的地址。)无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
可以利用#pragma pack()来改变编译器的默认对齐方式:
#pragma pack(n) //n=1,2,4,8,16…
枚举类型enum
enum Color { GREEN = 1, RED, //2 BLUE, //3 GREEN_RED = 10, GREEN_BLUE //11 }ColorVal;
函数
至少的函数头部
// 功 能: 改变缓冲区大小 // 参 数: nNewSize 缓冲区新长度 // 返回值: 缓冲区当前长度 // 说 明: 保持原信息内容不变
完整的函数说明
/************************************************************************ * Function Name : nucFindThread * Create Date : 2000/01/07 * Author/Corporation : your name/your company name * * Description : Find a proper thread in thread array. * If it’s a new then search an empty. * * Param : ThreadNo : someParam description * ThreadStatus : someParam description * * Return Code : Return Code description,eg: ERROR_Fail: not find a thread ERROR_SUCCEED: found * * Global Variable : DISP_wuiSegmentAppID * File Static Variable : naucThreadNo * Function Static Variable : None * *------------------------------------------------------------------------ * Revision History * No. Date Revised by Item Description * V0.5 2008/01/07 your name … … ************************************************************************/ static unsigned char nucFindThread(unsigned char ThreadNo,unsigned char ThreadStatus) { // TODO:... } //Blank Line
函数编写的一些注意:
- 保护性编程
- 缩进量统一使用4个字符
- 在一个函数体内,变量定义与函数语句之间要加空行
- 逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔
- 复杂的函数中,在分支语句,循环语句结束之后需要适当的注释,方便区分各分支或循环体 //end“for(condition)”
- 使用 assert 宏做函数入口校验 assert(NULL != p);
- 递归的深度太大可能出现错误(比如栈溢出)
- return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁
文件
文件命名:模块名缩写 + 小写字母名字
文件头部说明
/************************************************************************ * File Name : FN_FileName.c/ FN_FileName.h * Copyright : 2003-2008 XXXX Corporation,All Rights Reserved. * Module Name : DrawEngine/Display * * CPU : ARM7 * RTOS : Tron * * Create Date : 2008/10/01 * Author/Corporation : WhoAmI/yourcompany name * * AbstractDescription : Place some descriptionhere. * *-----------------------Revision History-------------------------------- * No Version Date Revised By Item Description * 1 V0.95 08.05.18 WhoAmI abcdefghijklm WhatUDo * ************************************************************************/ #ifndef __FN_FILENAME_H #define __FN_FILENAME_H #endif // Debug Switch Section // Include File Section // Macro Define Section // Structure Define Section // Prototype Declare Section // Global Variable Declare Section // File Static Variable Define Section // Function Define Section
之后的打算:
- 阅读《现代方法》(第2版)中关于预处理的部分
- 学习arm汇编
- 从汇编语言的层次理解C语言,姚新颜先生的《C语言:标准与实现》《C/C++深层探索》,关于X86汇编