C语言基础学习笔记

     --C语言框架--

数据  字节byte          字符   char         8
  半字half word     短整型        short int    16
    字word            整型   int(integer)   32
               长整型   long int     32
                 单精度   float        32
   双字(double word) 双精度  double       64
    ↓

 自定义类型:  结构体   struct **
    共用体             union  **
    枚举               emun
    ↓

 连续聚合:  数组   *** *[]
    字符数组(字符串)
    ↓
 特殊(32位) 指针 (point)     * (用于 间接寻址 访问变量)

 void 空类型

      数据:有类型便于管理内存和处理内存数据。(int)a
     地址:用于访问内存数据。必须有类型。(int *)&a
 控制:读,写。

结构流程:
 1.顺序:
 2.选择:单分支:if
    双分支:if……else    if控制的是与他最近的。
    多分支:if里嵌套
      else里嵌套
         switch……case
 3.循环:while(){break}, 
         for(;;;)


C代码结构:
  预处理1.头文件包含include<stdio.h>
         include"aaa.h"
     2.条件编译 #ifndef  AA
        #define AA
      ……
        endif
     3.宏定义  #define A 100
  全局变量定义 1.全局初始化
       2.静态全局,静态局部
       3.全局未初始化
  外部变量声明 (extern)
  函数声明 1.外部函数(extern)
       2.内部函数

  主函数段:
  int main(int argc,char * argv[])
  {
   局部变量存储类型 变量数据类型  变量名=数值;

   函数调用:本文件调用(static声明)
     外部文件 (extern声明)
     标准C库函数调用 ( include<>包含声明)
     系统函数调用

   return 0;
  }

  子函数段
  (static)int fun(char *a,int b)
  {
   ……
  }
             ……
 

    ********************************************************
            --C语言深度解剖--

---C语言基础---
字符:
字母:
数字:
下划线
特殊字符:
  算术运算符
  逻辑运算符
  位运算符
ASCII字符编码:
 ‘\0‘ ---0
‘\t’ ---9  ‘\n’ ---10 
 空格 ---32   0---48
 a---65      A --- 97

注意:
1.空的不一样:
 ‘\0’里面全为0,字符串结束标志
   空格为32,数字0为48
   char * p=NULL; 指针为空,里面也全是0
   EOF = -1 =ff ,即文件结束的标志,
2.反斜杠:‘\’
  1.断行连接符,编译器自动将下一行字符连接到上一行。注意:反斜杠后和下一行前不能有空格。
  2.转义字符, /n,/t,/a
    //,/‘,
              /ddd,/xhh,八进制和十六进制ASCII码,可表示任意一个C字符集中符号。

运算表达式:
运算符优先级:
1.(),[],->, .
2.* ,&, +,-,++,--,~,! (类型),sizeof
3.*,/ ,%                                      --算术运算
4,+,-
5,<<,>>                                       --(位移运算)
6.<,>,<=,>=                                   --比较运算
7,==,!=
8,&                                           --位运算
9,^
10,|
11.&&                                         --逻辑运算
12.||
13.?:
14.=,+=,-=,*=,/=,%=,&=,^=,|=,>>=,<<=          --赋值运算
15.,

注意:
1.if((++i>0)||(++j>0)) 或运算如果前面为真,后面不再运算。
  if((a==3)&&b++)      与运算,如果前面为假,后面不再运算。

2.++,--问题
i=3;
j=(++i)+(++i)+(++i);   ->  j= 16,先前两数相加,自加两次,为5+5,再加第三个数6.(跟编译器有关)

j=(i++)+(i++)+(i++);   ->  j= 9,i++要等到遇到分号才认为结束,自加。
j=(i++,i++,i++);       ->  j= 5,i++要等到遇到逗号才认为结束,自加。
for(i=0;i<10;i++)      ->  i=11,for循环中,i与10进行比较之后认为结束,自加

a=4;
a+=(a++)后a等于9    相当于a=a+(a++)
a+=(++a)后a等于10   相当于a=a+(++a)
注意:左边不能是(a++)或者(++a),会报错。

k*=i+j   相当于  k*=(i+j)即k=k*(i+j),*=是一个赋值符号。


贪心法:从左至右读入,直到读入字符不可能组成一个有意义的符号。
y=i+++j;       ->  y=(i++)+j;
y=++i++; (报错)
y=++i+++j;(报错)
j=++i+++i+++i;(报错)   ->
y=++i+(++j);           ->  y=(++i)+(++j)

 

 


3.优先级易错点:
  1..高于*:  *p.a                         表示*(p.a)
  2.[],()高于*: int *a[]                  表示int *(a[])指针数组 
                int *fun()                 表示int *(fun()) 指针函数
  3.==,!=高于位操作: (val&mask!=0)       表示val&(mask!=0)
  4.==,!=高于赋值运算: c=(getchar()!=EOF)  表示c=(getchar()!=EOF)
  5.算术运算高于位移运算:msb<<4+lsb         表示mab<<(4+lsb)
  6.逗号低于赋值,最低:i=1,2               表示(i=1),2

 

进制转换
00000000 -0 - 0
00000001 -1 - 1
00000010 -2 - 2
00000011 -3 - 3
00000100 -4 - 4
00000101 -5 - 5
00000110 -6 - 6
00000111 -7 - 7
00001000 -8 - 8
00001001 -9 - 9
00001010 -10 - a
00001011 -11 - b
00001100 -12 - c
00001101 -13 - d
00001110 -14 - e
00001111 -15 - f

 

标识符:函数名,变量名,常量名,数组名,类型名,文件名
 只能用字母或下划线开头,不能用数字开头

 

常量:1.直接类型常量(数值常量(整型,实型)+字符类型常量(字符,字符串))
      2.符号常量(#define)
变量:

 

关键字(保留字)32个:
  extern
  变量存储类型:
 auto  static register const volatile
  变量数据类型:
     signed unsigned
     void char short int long float double  struct union enum
  流程结构控制:
  if else switch case defaut do while  break continue goto for
  return

  其他:
 typedef sizeof

 extern用于变量和函数的声明,来自外文件
  extern fun1(int ,int)
  extern int i;声明--声明该变量在外部已经定义,可以使用。
  int i=0;定义--分配一块可用内存,只能定义一次。用i代表一段4字节内存。

static:避免全局变量名字重复,和错误的变量引用。
(存于.data段,符号表中Bind为LOCAL,变量和函数等标示只能用于本文件,不能被链接)
    1).修饰变量(将变量存在.data段,可读可写)
   1.静态全局变量(静态分配,用于该文件定义之后)
   2.静态局部变量(静态分配,只能用于定义它的函数)
    2).修饰函数:函数仅用于本文件。
   
const:==readonly 只读变量,并非常量(int a[M]不行)!
1.一般变量:a.局部变量   不会放于.rodata段,动态分配,只读
  b.全局变量  (存于.rodata段,数据只读)const int a ==int const a;
2.数组const int a[5]== int const a[5] ;
3.指针 const int *p =int const *p :p所指向内容不能通过p改变(不能*p=2)
   int * const p :p本身不能变(不能p=&b)
  const int * const p :都不能变

4.const修饰函数输入参数:
void Fun(const int *p);表示*p在函数体中不能改变,防止意外修改。
    const char *p="hello world"也要用const

5.const修饰函数返回值:
const Fun(void); 表示函数返回值不能改变。


volatile:不稳定的,可变的
           这种变量的值在每一次参与运算的时候都会到内存中去读取实际的值,而不会使用寄存器中原来的值;该种类型的值可以和外部的变化同步
      在什么样的情况下需要使用该种类型?
      实时系统中间--实时数据采集
      多线程*享的变量
      cpu的寄存器:控制寄存器、状态寄存器
      中断处理程序中所用到的数据

void

1.void+变量:报错,任何变量都需要有类型,不然不知道分配多少内存。
  void * P :1.p++在ANSI中报错,但是在GNU中把他当做char类型,可以p++;
  2.void类型指针可接受任何类型变量地址,不能赋值给有类型的指针。
              用的时候必须强制地址转换。 


2.函数:void fun(void)
        1:返回值有void时表示没有返回值,不能return。
          不写时,默认为int类型而不是void。
        2.参数为void时,表示不能接收任何参数,
          参数不写时,C语言表示可以接收任何类型任何个数参数。C++表示不接受任何参数。
        3.编写的时候不要省,没有应该加上void。“自注释”。

 

char
  char 字符型
  signed char 数值型 0~255
  unsiged char 无符号数值型  -128~127

sizeof
   sizeof 关键字,在编译的时候求出任意数据类型+变量所占内存空间大小(包括\0.)
   strlen  库函数,运行时求字符串(有‘\0’)的长度,遇到\0结束(不包括\0.)。

   int a[4];   sizeof(a)中a是代表内存空间,而strlen(a)中a代表首地址(指针)。

   sizeof(int)
   sizeof(i)  
   sizeof i; 函数则一定有括号,说明不是函数
     注意:sizeof int 是错的。
  
   sizeof(int)*p :  意思是  4*p
 

char str[] = “http://www.ibegroup.com/”
char *p = str ;
int n = 10;
sizeof (str ) = 25
sizeof ( p ) = 4
sizeof ( n ) = 4

void Foo ( char str[100])等效于 char str[]和char *str
{
   sizeof( str ) = 4  数组给函数永远传首地址,函数参数都是指针(4字节)。
}

void *p = malloc( 100 );
sizeof ( p ) = 4

---流程结构---:
顺序:

条件:if,else  switch case
注意:就近原则:if只管到最近的一个分号语句
       else与上面最近的一个if配对

循环:for,while ,do while

控制:break,continue,goto
break:终止本层循环,一般用在switch-case,while(1)中
continue:结束本轮循环,一般用在while中。

函数返回:return (Val)
注意:局部变量不能返回地址。

---指针---
定义:int *pa;一级指针 int **p; 二级指针
 定义并初始化: int *p=NULL; int *pb=&a
赋值:pa=&a; pa=pb;
取值:c=*pa;

细节注意:
1.不管什么类型的指针,不管多少级(如char ***a)在32位平台是都是固定占32个字节,4个字节。

2.void *pa=&a;  void类型指针可以接受任何类型地址,但是使用时应强制转换
  (int *)pa       (*(char *)pa)
  也就是说指针使用的时候必须知道它的类型(地址所引用的数据范围)
3.const: const int *pa;  *pa值不变
    int  *const pa;  pa地址不变
4.指定地址赋值:
    1.int *p=(int *)0x80000000;
      *p=(int)0x78;
    2.*(int *)0x80000000=(int)0x78;

---数组---

变量:
空类型void
基本类型:char short int long float double
自定义构造:(结构体 struct 联合体 union 枚举 enum)
聚合:数组 **[]: 有序,长度固定,类型相同

定义:int a[3];  int a[]={1,2,3}; int str[]={‘a‘,‘b‘}
输入输出:for(i=0;i<n;i++)
取值方式:1.下标:以具名+匿名方式访问
  a[i]:i为距离首地址a的偏移量,等同于*(a+i);
    2.指针:以匿名方式访问
  pa[i]:pa=a;等同于*(pa+i);
  区别:a是地址常量, 值不能变
   pa是指针变量,有独立内存空间,值可以改变,如:*(pa++) 


指针数组:int *a[2]={&a,&b}; 本质是一个数组,里面每个元素存放的是地址
数组指针:int (*a)[3]=&a;  本质是一个指针,指向的是一个数组(int (*)[3] a=&a)


int a: a代表一块类型为int的内存
    &a为其首字节地址(入口地址)
int a[3]:a(1)a代表 3*4的一整块内存
      (2)a不能是左值,a为右值时,为了方便取值,则a为首元素首地址。a不能变
  a=&a[0]
     &a为整块内存首地址。sizeof(a)
int a[3][4]:

细节注意:
1.给指定地址赋值: int * p =(int *)0x67a9;地址必须给他类型。
                   *p=(int)0xaa66;//或者*p=0xaa66;
    等价于:*(int *)0x67a9=oxaa66;


3.*p++相同优先级,右结合,先算p++,再*p,此处p先取值后自加。

3.指针和数组:定义为指针,在别的地方声明也只能是指针;定义为数组,在别的地方声明也只能是数组。

---字符串---
一种特殊数组
定义:1.char name[5]={‘c‘,‘h‘,‘i‘,n‘‘,‘a‘}; 字符数组方式定义。
      2.char ch[]="china"; 字符串方式定义,编译器自动加‘\0‘;
        char p=ch;         "china"为局部变量保存在栈里,通过p指针可读可写。
 3.const char *p="china";  "china"保存在.rodata段,为常量不可写,返回其首地址
            用的时候相当于数组名。

输入输出:
scanf("%s,%s",s1,s2); 可以连续输入多个,以空格或‘\n’区分
gets(s1);             只能输入单个,遇到空格或‘\n‘停止,自动加 ‘\0‘结束符,‘\n’不保存;
printf("%s %s",s1,s2);可以连续输出多个
puts(s1);             只能输出单个

命令行参数:
int main(int argc,char * argv[])
int argc:为参数的个数
char * argv[] 字符指针数组
./a.out 1 2 3  ->  argc=4,argv[0]="./a.out"; argv[1]="1";argv[2]="2";argv[3]="3";


细节注意:
1、const char a[]="abcde";
   char   a1[]="abcde";
   const char * b="abcde";
   char  * b1="abcde";
   a!=a  b==b1;

---函数---
定义:返回值类型  函数名 (形参)
 1.int fun(int a,int b)
        {
            函数体
        }
       2.int *fun (int *a)   返回为一个指针,形参为指针
    {
    }


声明:int fun(int ,char *);  函数使用前必须有定义,或者先声明,后定义。

使用:fun(a,b)  ; fun(&a,&b);

参数传递:
参数传递只是作备份,无法把指针本身传递给函数。
1.简单值传递  void fun(int a)    -> fun(int a);
2.指针(地址) void fun(int *a)  ->fun(&a);
3.数组 :用的是指针传递,效率高
       值:   void fun(char a)  ->fun(a[i]);
      地址:   一维:void fun(char * a)  (char a[6])(char a[])   -> fun(str);
    二维:void fun(char (*s)[2]) (char s[10][2])(char s[][2]) ->fun(str);

4.字符串: void fun(const char * p)   -> fun("hello world!");

细节注意:
1.递归调用。

2.int *f1(void)
{
  int x =10;
  return(&x);
}//局部变量不能返回地址

3.传值调用和传址调用
传值调用:数值复制,形参改变而实参不变。(不能改变变量值)
传址调用:虽地址复制,但是通过同一地址依旧可以直接改变实参的值。(能改变变量值)
(C++中有引用传递)

4.不用任何变量编写strlen函数:
int  MyStrLen(const char * str)
{
   assert(NULL!=str);  //用assert进行入口检查
   return(‘\0‘!=*str)?(1+MyStrLen(str+1)):0; //用函数递归,但是效率低,内存开销大,最好不用。


指针函数:void *fun(void)  返回值为指针的函数
函数指针  void (*fp)(int a) 指向一个函数的指针,(等同于:void (*)(int a) fp);
          pf=&fun1; 或者pf=fun1;   (*p)(2)调用函数
          用void (*p)();  *(int *)&p=(int) Fun ;   (*p)(); 也是一样的。
函数指针数组:void (*fp[3])(int a) 指向3个函数的指针数组。
函数指针数组指针 :char * (*(*pf)[3])(char *p);

(*(void(*)())0)() 定义了一个 void(*)()类型的函数指针,调用0地址的函数
(*(char **(*)(char **,char **))0)(char **,char **);


---预处理---


宏定义:(在预处理的时候简单替换)
无参:#define  M (100)
有参:#define L(x) ((x)*(x)+2)

常用的一些宏定义:
 1.判断数组a元素的个数:  #define N(a)  (sizeof(a)/sizeof(*a))
 2.MIN:  #define MIN(a,b) ((a)<(b)?(a):(b))
 3.SWAP:  #define SWAP(a,b)  (a) =(a)+(b);(b)=(a)-(b);(a)=(a)-(b)
 4.#define SECONDS_PER_YEAR (365*24*60*60)UL


注意:1.可以定义符号常量:(数值常量,字符串常量等),不能定义注释符号。注释先于预处理。
 2.可以宏定义变量类型,函数等,但是一般用typedef,函数比较好。
 2.define定义表达式时,每个变量和最外层要加括号,L(x)之间不能有空格(用的时候可以L (3)).
 3.可以用#undef取消命令,但是一般不用。

注意: 1.define是预处理命令,不需要加分号。
 2.打括号,简单宏替换容易出错。
      3.标示符大写,多个组成用_隔开
      4.长整型可以用 UL 标记
       #define SECONDS_PER_YEAR (365*24*60*60)UL


#define与const
在数据定义时,define是常量,而const是只读变量(int a[M]不行);
const优点:1.编译器可以进行类型安全检查
           2.有些集成的调试工具可以对const常量进行调试。
           3.节省内存,提高效率,如下:
#define M 2;  提高程序可移植性,修改方便
int i=M;在赋值时还得先分配2内存空间
const int N=2;
int j=N;直接用N在.rodata的地址赋值给j,节省内存,提高效率。


#define与typedef
在类型定义时,#define只是做了简单的替换,容易出错。而typedef定义了一种新类型.
#define U  int p*
tpyedef int p*  U;
U p1,p2;此时在#define中就会出错了,#define int p* p1,p2;


#define 与inline 与 函数
define不需要跳转传参,节省了时间,但消耗了空间
函数:节省了空间,消耗了时间
inline:代码量小,不需要跳转传参,优化了时间和空间

#define与enum
1.枚举一次可以定义多个常量,宏常量只有一个。
2.#define在预编译时替换,枚举在编译时替换
3.枚举常量可以调试,宏常量不能调试

 

 

文件包含:
#include <>  表示到包含文件目录去寻找
#include ""  表示到当前源文件目录寻找

条件编译:
#ifdef 标示符 (#ifndef A):防止头文件重复包含
   ...
#else
   ...
#endif

#if 常量表达式
   ...
#else
   ...
#endif

其他:
1.#error:编译程序时,只要遇到#error就会生成一个编译错误提示消息,并停止编译
 #error error-message
2.#line:改变当前行号和文件名称
      #line number[filename]
#pragma:设定编译器完成一些特定的动作
   #pragma para
   #pragma message(“消息文本”)
    #ifdef_x86
    #prama message("_x86 macro actvated!")  //如果定义了_x86,则在编译的时候输出信息。
    #endif

注意:

 

 


---结构体,联合体,枚举---

结构体:
1.在网络传输中,要传输的内容用结构体打包。
2.函数的参数,用结构体打包效率高。

声明:struct Student
    {
    int num;
    char name[20];
    float score;
    };此处可以定义一个变量,但是存储类型是全局变量

定义:struct Students2;
      stuct  Student s[3];结构体数组

使用:1.s1.num
      2.struct Student * p=&s1;
       p->num
     3.(*p).num

大小计算:1.自然对齐:(以最大成员类型对齐)
          2.空结构体大小在gcc中编译是0,其他编译器可能是1.
          3.里面有柔性数组int a[0]或int a[] 时,大小并不包括柔性数组成员。
         2.预处理命令#pragma  pack(n) 限制内存对齐系数

内存对齐:
编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能
地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问
;然而,对齐的内存访问仅需要一次访问。

结构体大小计算:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,
如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最
末一个成员之后加上填充字节(trailing padding)。

8含位域结构体的sizeof
使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

联合体的sizeof
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个
联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这
里,复合类型成员是被作为整体考虑的。

由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将
复合类型作为整体看待。


struct S1
{
char c;
int i;
};

struct S2
{
char c1;
S1 s;
char c2
};

union S3
{
char c1;
S1 s;
char c2
};

sizeof(struct S2)=16
sizeof(union S3)=8

总结:对齐的时候按基本类型算。算大小的时候才看做一个整体。

位结构体:
struct Net
{
 char a:2;
 char  :0; //空域
 char c:3;
}

struct Net
{
 char a:2;
 char  :2;//空域
 char c:3;
}


struct Net
{
 char a:2;
 char  :5;//空域
 char c:3;
}

联合体(共用体)
声明:union Student
     {
  char a;
  char name[7];
 }

1.所有成员公用一个最大的类型空间
2.所有成员使用该空间


枚举:
相当于用一个枚举类型,里面声明了一些枚举常量(标示符而已),相当于常量整数。
枚举常量在声明时可以给值,如果不给值,则默认从0递增。
当定义一个枚举变量时,只有一个位置来存放声明的多个枚举常量中的一个,大小为4字节。

声明:enum ColorName{ RED,YELLOW, GREEN};
 enum ColorName{ RED=3,YELLOW, GREEN};
定义:enum ColorName color; 变量color只有一个位置,只能存放几个枚举常量之一
使用:color=RED;
 for(color=RED;color<GREEN;color++)

注意:1.printf("%d",RED);对,枚举常量相当于整数
  printf("%s",RED);错误,枚举常量不是字符串


---位运算---

按优先级排列:
取反:~
左移:<<
右移:>>
按位与:&
按位异或:^
按位或:|

 

 

 

 

    ********************************************************
               --C语言本质--

用二进制工具可查看:
readelf -a hello.o   读取elf文件信息
hexdump -C hello.o   elf文件十六进制代码
objdump -d hello.o   机器指令反汇编

ELF: 可执行可链接格式 Executable and Linkable Format
     UNIX 应用程序二进制接口(Application Binary Interface,ABI)
   ELF header
   program header table    可执行文件.exe ,用来加载执行
   ...
   ...
   section header table     目标文件.o:  用来链接

检验代码:
#include <stdio.h>

int a;   //保存在.bss,不分配内存。

int b=2;   //保存在.data
char str1[]="liaoyi1";  //保存在.data
static int c=3;         //保存在.data,符号表中Bind为LOCAL

const int d=4;               //保存在.rodata
const char a[]="liaoyi3";    //保存在.rodata
char *p1="liaoyi2";          //保存在.rodata

int main()
{
 int e;
 int f=5;
 static int g=6;         //保存在.data,符号表中名字为.g1639,Bind为LOCAL
 const int h=8;
 char str2[]="liaoyi3";
 char *p2="liaoyi4";     //保存在.rodata
 
 return 0;
}


预处理.c->.i 将代码处理成完整的c代码

编译 .i->.s 将c代码编译成汇编代码

汇编 .s->.o :将汇编代码汇编成二进制目标代码(多个Section二进制文件)
  此时段的加载地址和跳转的内存地址都是相对地址,在链接时改为内存虚拟地址
elf header(体系版本大小端,还有两个头的大小和起始地址)
section header table(描述每个段在文件中的编号,大小,相对地址。以及要加载到内存的地址(此时为空))
.text(代码段)
.data (数据段)
.bss(空,加载时初始化)
.rodata (const常量,字符串常量)
.comment(编译工具,运行系统平台信息)
.note.GNU-stack(一般为空)
.shstrtab(保存段头名,ASCII码形式保存)
.symtab(符号表:每个strtab的值Value为其所代表的地址,以及是限制符Bind为LOCAL(本文件)还是GLOBAL,LOCAL即static修饰变量和函数,静态局部变量名字会加编号,如a.1314来区别静态全局变量,LOCAL不能被链接)
.strtab(保存程序中符号名(如跳转标识(变量名,函数名),ASCII码形式保存)
.rel.text(哪些地方要重定位,在.text中有两处相对地址需要改)
.rel.data
 
 
链接 .o->exe

elf header(type变成exec)
section header table(加载地址变为了内存地址)

...各种新生成的段。

.text
.rodata
.data
*.bss(没有用到bss数据被删除)
.comment
.shstrtab
*.rel.test(链接后重定位没了)
.symtab(增加_bss_start,_edata,_end,在加载时利用这 些信息可将bss段初始化为0;)
.strtab
program header table  (每个segment的大小,加载地址,权限)
 elf header + progream header table + .text+.rodata    ->只读
 .data .bss                                             ->可读可写

加载:按页加载,并且每个segment和文件中的相对地址保持偏移一致

 

 


内存分布:(Linux中)
高地址

*register变量直接把保存到寄存器中,也直接从寄存器中取值。速度快。

stack:动态分配,int a[]="hello world!"; "hello world "是一个常字符串,会在.rodata中有备份,但       是给a数组初始化的hellowrold存放于指令中,指令中的数据       存到栈中,a右值代表其栈中首元素内存地址。
                          注意:全局的int a[]="hello world!"; 不会保存在.rodata 
     int *p="hello world!"; p保存hello world在 .rodata中的首地址
      
 一般基本变量是高地址到低地址,先进后出(递减栈)
 数组是低地址到高地址
 i
 j
 k
 a[2]
 a[1]
 a[0]
 
heap:
 手动分配,手动释放
 int *p=(int *) malloc(4* sizeof(int));

可读可写segment:
.bss :全局变量未初始化,在加载时赋0; int a;
.data :int a=2; 全局变量已初始化
        static int b=3;静态全局变量,LOCAL,不能被链接器处理,只能在本文件使用。
   main函数中:static int a=4;静态局部变量,在符号表中为:a.1598,静态分配,但在那个函数中     起作用。

只读segment:
.rodata :"hello world", const int a=2;  只读数据
.test :代码段


低地址


---对堆和栈的理解和区分---
(不是单纯的malloc申请的那个堆内存)

操作系统对内存的操作:
堆:顺序随意
栈:先进后出
堆和栈的区别
一、程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
2、堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放 
5、程序代码区—存放函数体的二进制代码。

二、堆和栈的理论知识
2.1申请方式
stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:需要程序员自己申请,并指明大小,在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符如p2 = (char *)malloc(10);但是注意p1、p2本身是在栈中的
2.2申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活 
2.5堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 
2.6存取效率的比较 
char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的;而bbbbbbbbbbb是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 

2.7小结:
1.栈系统自动分配,堆手动分配
2.栈能分配的地址小而连续,由高到底。链表能分配的地址大而不连续,由底到高。
3.栈分配速度快,无法控制,链表分配速度较慢,使用*。

堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是*度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且*度大。


 
数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。

 

 

    ********************************************************
            --C数据结构与算法--

数据结构:


1.链表
单向链表
双向链表
单向循环链表
双向循环链表

2.栈:先进后出
线性栈(倒立数组)
链表栈(倒立链表)


3.队列:先进先出
线性队列
链表队列

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


面试:
1:外表:1.学生装 2.干净,整洁,无异味。
2:内在:1.礼貌 2.谦虚 3.真实
3:简历:1


a.软实力:
b.硬功夫:
c.小工程:
d.大行业:

 

 

 

 

 

 


 

C语言基础学习笔记,布布扣,bubuko.com

C语言基础学习笔记

上一篇:关于编程语言类型系统


下一篇:美国深泉学院(Deep Springs College)