您可以通过左右滑屏查看更多笔记内容。。

C

22 Jul 2019 . category: tech .
更多笔记

Linux C语言-通过管道打造实用小程序

实现输入n个数据得到数据的平均值;

  1. 编写一个计算平均值的小程序;

    1. mkdir lens6-->vi avg.c

    2. #include <stdio.h>
      int main(){
          //求平均值;
          int s,n;
          scanf("%d,%d",&s,&n);
          float v=s/n;
          printf("v=%f\n",v);
          return 0;
      }
      wq-->gcc avg.c -o avg.out-->./avg.out(测试可用)
      
  2. 编写一个计算用户输入数据的个数与和的小程序;

    1. #include <stdio.h>
      int main(){
          int flag=1;
          int i;
          //计算用户输入的数据个数;
          int count=0;
          //计算所有数据的和;
          int s=0;
          while(flag){
             scanf("%d",&i);
             if(0==i) break;
             count++;
             s+=i;
          }
          printf("%d,%d\n",s,count);
          return 0;
      }
      wq-->cc input.c -o input.out-->./input.out(测试可用)
      
  3. 结合以上两个小程序,实现,输入n 个数据,知己计算n 个数据的平均值;

    • ./input.out | ./avg.out
    • 输入测试数据 :100 ,100,100,100,100
    • 得到结果:v=100.000000

#### “ * ”和”&” 区别

**” * “:指针 “&” 引用 **

概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。

★相同点: ●都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

★不同点: ●指针是一个实体,而引用仅是个别名; ●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可 以“见异思迁”; ●引用没有const,指针有const,const的指针不可变; ●引用不能为空,指针可以为空; ●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小; ●指针和引用的自增(++)运算意义不一样; ●引用是类型安全的,而指针不是 (引用比指针多了类型检查)

指针

指针:

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元;

使用指针的案例

  • 将两个数a ,b 进行交换;
  • 若没有在a,b 前加*““&”*,则结果未进行交换,若加上“”后结果进行了交换;
  • “*”是指针的一种表达形式;
#include <stdio.h>
//在a,b前加*
void change(int *a,int *b){
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
int main(){
    int a=3;
    int b=5;
    //在a,b钱加&
    change(&a,&b);
    printf("num a=%d\nnum b=%d\n",a,b);
}

gdb工具

一、gdb:

UNIX及UNIX-like下的调试工具:

功能:一般来说,GDB主要帮助你完成下面四个方面的功能:

1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。

2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式

3、当程序被停住时,可以检查此时你的程序中所发生的事。

4、你可以改变你的程序,将一个BUG产生的影响修正从而测试其他BUG。

二、gdb常用指令:

gcc -g  main.c                      //在目标文件加入源代码的信息,
-g                                  //-g后可调试文件
gdb a.out        
(gdb) start                         //开始调试
(gdb) n                             //一条一条执行
(gdb) step/s                        //执行下一条,如果函数进入函数
(gdb) backtrace/bt                  //查看函数调用栈帧(查看函数堆栈)
(gdb) info/i locals                 //查看当前栈帧局部变量
(gdb) frame/f                       //选择栈帧,再查看局部变量
(gdb) print/p                       //打印变量的值
(gdb) finish                        //运行到当前函数返回
(gdb) set var sum=0                 //修改变量值
(gdb) list/l 行号或函数名             //列出源码
(gdb) display/undisplay sum         //每次停下显示变量的值/取消跟踪
(gdb) break/b  行号或函数名           //设置断点
(gdb) continue/c                    //连续运行
(gdb) info/i breakpoints            //查看已经设置的断点
(gdb) delete breakpoints 2          //删除某个断点
(gdb) disable/enable breakpoints 3  //禁用/启用某个断点
(gdb) break 9 if sum != 0           //满足条件才激活断点
(gdb) run/r                         //重新从程序开头连续执行
(gdb) watch input[4]                //设置观察点
(gdb) info/i watceping/248chpoints            //查看设置的观察点
(gdb) x/7b input                    //打印存储器内容,b--每个字节一组,7--7组ceping/248ceping/248
(gdb) disassemble     ceping/248              //反汇编当前函数或指定函数
(gdb) si                            // 一条指令一条指令调试 而 s 是一行一行代码
(gdb) info registers                // 显示所有寄存器的当前值
(gdb) x/20 $esp                     //查看内存中开始的20个数
(gdb) q                             //退出
  • a,b在两个函数中是局部变量。 ceping/248

  • 通过gdb调试ceping/248案例可以查看*a 的值,是地址;

  • 查看未加*时的方法

    • (gdb) s
      change (a=3, b=5) at main2.c:4
      4	    int tmp=a;
      
  • 查看加*时的方法

    • (gdb) s
      change (a=0x7fffffffdeb0, b=0x7fffffffdeb4) at main.c:4
      4	    int tmp=*a;
      
    • 其中0x7fffffffdeb0,0x7fffffffdeb4 是a,b的地址

    指针与内存

计算机中的数据表示方法

  • 计算机中数据表示方式:二进制;(满二进一,只有0 1);

  • 计算机中最小单位是“字节(byte)” 1byte=8bit
  • 使用:计算用二级制,显示为十进制;编程用十六进制
  • **一个十六进制的数字,可以表示为4位二进制的数字! **
  • 十六机制:一般用数字0到9和字母A到F(或a~f)表示,其中:A~F表示10~15,这些称作十六进制数字

内存

  • 所有的程序都是由操作系统调用的;
  • main()函数是所有函数的入口-->操作系统知道入口后就能执行代码-->程序被调用;
  • 操作系统:
    • 除了能给内存做编号以外;
    • 还可以给内存做一定的规划;
    • 例如:在64位操作系统中,程序员使用的内存只要有前面的48位就可以了!大于这个的地址的内存空间是给操作系统内核使用的;
  • 用户内存隔离开的好处:
    • 操作系统的内存不会被大量占用;
    • 避免机器卡住,卡死,死机等状态;
    • 可通过操作系统把应用程序关闭;
    • 使得操作系统更安全;
  • 编写C语言代码-->函数编译之后存到磁盘-->代码编译后的二进制数据加载到内存中

指针:本质就是地址:

变量:本质就是内存;


&是取地址运算符

*pa=&a与pa=&a之间的疑惑, *pa=&a 是把a的地址值 给指针pa里的数据赋值. pa=&a是把a的地址值赋值给pa,使得指针pa(指向)a的地址 可以理解为 *pa=&a,是“值  

int *pa是指定义一个指向int类型的指针pa。&在这里是取址的意思&a就是取a的地址。所以int *pa=&a;意思是定义一个指向变量a的指针pa

例如:(难点)

#include <stdio.h>
int global=0;
//计算矩形的面积
int rect(int a,int b){
    static int count=0;
    count++;
    global++;
    int s=a*b;
    return s;
}
//计算正方形的面积
int quadrate(int a){
    static int count=0;
    count++;
    global++;
    int s=rect(a,a);
    return s;
}
int main(){
 int a=3;
 int b=4;
 int *pa;
 pa = &a;
 int *pb=&b;
 int *pglobal=&global;
 int (*pquadrate)(int a)=&quadrate;
 int s=quadrate(a);
 printf("%d\n",s);
 }
 wq-->gcc -g main.c -o main.out-->gdb main.out-->start-->n-->n-->n-->...

(gdb) p a $1 = 3 (gdb) p &a $2 = (int *) 0x7fffffffde9c (gdb) p pa $3 = (int *) 0x7fffffffde9c (gdb) p *pa $4 = 3

内存

操作系统对内存的管理

/*连续声明了a,b两个变量,为什么不连续?
a,b 都是整型数据,占32位,即4个字节,默认打印所占的第一个字节,a所占的(de9c,de9d,de9e,de9f),所以到b应该是(de9g),但十六机制满16进一,所以,变为(a1,dea1,dea2,dea3);*/
db) p &a
	$4 = (int *) 0x7fffffffde9c
(gdb) p &b
	$5 = (int *) 0x7fffffffdea0
	
/*pa的地址为s的地址增加4后的地址,但pa,pb,pglobal为内存地址,在64位地址总线下,一个内存地址,占8个字节*/
(gdb) p &pa
	$6 = (int **) 0x7fffffffdea8
(gdb) p &pb
	$7 = (int **) 0x7fffffffdeb0
(gdb) p &pglobalneicunneicun
	$10 = (int **) 0x7fffffffdeb8
	
/*s的地址是b地址加+4后的地址;
gcc编译器进行优化,当我们在一个函数中,有若干个整型、浮点型等或其他类型变量时,gcc 会将相同类型的变量先声明,在声明其他类型的变量,所以先声明了所有的整形变量,再声明的指针变量!*/
(gdb) p &s
	$9 = (int *) 0x7fffffffdea4
	
/*函数也是一个内存地址,所占也是8个字节*/
(gdb) p &pquadrateneicunneicun![1563720540818](/home/lixuehe/.config/Typora/typora-user-images/1563720540818.png)
	$1neicun1 = (int (**)(int)) 0x7fffffffdec0
neicun
栈:保存的是函数运行时的状态;neicun
包括代码执行的行数,每个内存空间中的具体数值,在栈内一个函数可被多次调用,每次调用就是一个新的栈,

函数栈以及数据段内存

代码段和数据段都是从下向上分配,内存地址越来越大;

栈相反,最先分配的栈地址最大,栈的特点:先进后出

/*栈内是先进后出,最先分配的地址最大,以下可以看书,main的内存地址最大,向上依次减少*/
(gdb) bt
	#0  rect (a=3, b=3) at main.c:7
	#1  0x0000555555554719 in quadrate (a=3) at main.c:18
	#2  0x0000555555554776 in main () at main.c:31
(gdb) p &rect
	$5 = (int (*)(int, int)) 0x5555555546aa <rect>
(gdb) p &quadrate
	$6 = (int (*)(int)) 0x5555555546e1 <quadrate>
(gdb) p &main
	$7 = (int (*)()) 0x555555554721 <main>
	
/*以下结果可以看出,在两个函数中,count值不相同,global值相同
 1. 因为count 是局部静态变量,global是全局变量;静态局部变量是某个函数总特有的,全局变量是所有函数共有的;
 2. 静态变量,常量,全局变量都在数据段中;
 3. 即使一个函数被调用多次,其中的静态变量还是指向数据段中的一个固定的地址,虽然不同函数的count值不同,但同一个函数中的count值不管被调用几次,指向地址不会改变;  
*/
(gdb) f 0
	#0  rect (a=3, b=3) at main.c:7
	7	    count++;
(gdb) p &count
	$1 = (int *) 0x555555755018 <count>
(gdb) p &global
	$2 = (int *) 0x555555755014 <global>
(gdb) f 1
	#1  0x0000555555554719 in quadrate (a=3) at main.c:18
	18	    int s=rect(a,a);
(gdb) p &count
	$3 = (int *) 0x55555575501c <count>
(gdb) p &global
	$4 = (int *) 0x555555755014 <global>