算法笔记4.函数和递归结构体(gdb查看调用栈

4函数和递归结构体

P66

学习目标

!多参数,单返回值的数学函数定义和使用方法

typedef定义结构体

assert宏帮助调试

函数调用时用实参给形参赋值过程

定义局部变量/全局变量

!调用栈和栈帧,用gdb查看调用栈并选择栈帧

地址和指针

递归定义和递归函数

*可执行文件中正文段/数据段/BSS

*堆栈段,栈溢出的常见原因

数学函数

函数的返回类型最好是int/double/char(特殊int),若是数组则较为麻烦

hypotx,y:计算直角三角形斜边长(不是ANSI C的函数)

使用结构体函数

struct Point{

         double x,y;

};

double dist(struct Point a, struct Point b){

         return hypot(a.x-b.x,a.y-b.y);

}

重定义方法定义使用:

typedef struct{

         double x,y;

} Point;

double dist(Point a, Point b){

         return hypot(a.x-b.x,a.y-b.y);

}

定义方法:struct 结构体名{域定义;};

注意花括号后有分号

重定义法定义:typedef struct{域定义;}类型名;

即使最终答案在数据类型的范围之内,中间的计算结果仍然可能溢出

结构体成员变量的访问

如果通过结构体指针访问结构体,或结构体成员变量本身是指针,可以用->

否则用.

Error: base operand of '->' has non-pointer type ''

结构体操作函数

Memcpy 头文件string.h

strcpymemcpy主要有以下3方面的区别。

1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

 

4.1组合数

解决溢出(利用组合数两端对称特性

#include<stdio.h>

int factorial(int n,int m){

         int i,res=1;

         for(i=n;i>=m;i--){

                  res*=i;

         }

//     printf("%d\t%d\t%d\n",n,m,res);

         return res;

}

int main(){

         int m,n,res;

         scanf("%d%d",&n,&m);

         if(m>n/2){

                  res=factorial(n,m)/factorial(n-m,1);

         }else{

                  res=factorial(n,n-m)/factorial(m,1);

         }

         printf("%d",res);

         return 0;

}

谓词、for条件多次计算、assert

谓词:判断一个事物是否具有某个性质(数字是否是素数)

谓词函数编写一般规则:命名“is_xxx”,返回int0表示假,非0表示真

尽量保证对任何参数都能得到正确结果,否则表明缺陷,以免误用

判断素数技巧:1.x%i==02.枚举因数时不超过sqrt(x),若超过,则乘积大于x3.发现因数及时退出

特殊数:n=1会被判断为素数,太大数i*i计算边界时超过int最大值,溢出变成负数

使用sqrt不易溢出,但要处理精度误差。m=floor(sqrt(x)+0.5);

使用assert.h中的assert宏限制非法的函数调用,传入函数不符合条件则程序异常终止,并给出提示信息(帮助调试检查参数):assert(x>=0);不符合x>=0则退出

在实际系统中不能因为一个地方参数错误,整个程序异常退出。在编写和调试时可通过assert帮助提升程序质量

4.2孪生素数

#include<stdio.h>

#include<math.h>

int prime(int m){

         int i;

         for(i=2;i<=sqrt(m);i++){

                  if(m%i==0)return 0;

         }

         return 1;

}

int main(){

         int m;

         scanf("%d",&m);

         if(m%2==0)m--;

         for(;m>2;m-=2){

                  if(prime(m)&&prime(m-2))break;

         }

         printf("%d %d",m-2,m);

         return 0;

}

改进:

#include<stdio.h>

#include<math.h>

#include<assert.h>

int is_prime(int x){

         int i,m;

         assert(x>=0);

         if(x==1)return 0;

         m=floor(sqrt(x)+0.5);

         for(i=2;i<=m;i++){

                  if(x%i==0)return 0;

         }

         return 1;

}

int main(){

         int m;

         scanf("%d",&m);

         if(m%2==0)m--;

assert(m>=0);

         for(;m>2;m-=2){

                  if(is_prime(m)&&is_prime(m-2))break;

         }

         printf("%d %d",m-2,m);

         return 0;

}

指针与地址

函数的形参和函数内声明的变量都是函数的局部变量。无法访问其他函数的局部变量。存储空间临时分配,函数执行完毕释放。

函数外声明的变量是全局变量,可以被任何函数使用

调用栈call stack)由多个栈帧组成,每个栈帧对应着一个未运行完函数。在gdb中可以用backtrace(简称bt)打印所有栈帧信息,若要用p命令打印一个非当前栈帧的局部变量,可以用frame命令选择另一个栈帧

gdb是源码级调试器

编译程序:

gcc test.cpp -o test -Wall -g

运行gdb

gdb test.exe

查看源码

l

显示:

(gdb) l

1       #include<stdio.h>

2       void swap(int a,int b) {

3               int t=a;a=b;b=t;

4       }

5       int main()

6       {

7               int a=3,b=4;

8               swap(a,b);

9               printf("%d %d\n",a,b);

10              return 0;

swap函数第4行结束,但还没有返回)

加断点并运行:

(gdb) b 4

Breakpoint 1 at 0x401550: file test.cpp, line 4.

(gdb) r

Starting program: C:\Users\laugo\test.exe

[New Thread 5004.0xa20]

[New Thread 5004.0x21c0]

 

Breakpoint 1, swap (a=4, b=3) at test.cpp:4

4       }

(碰到断点在第四行停止,这时)

查看调用栈

(gdb) bt

#0  swap (a=4, b=3) at test.cpp:4

#1  0x0000000000401580 in main () at test.cpp:8

包含两个栈帧:#0#11号是0的上一个栈帧,0x0000000000401580swap函数的返回地址

(gdb) p a

$1 = 4

(gdb) p b

$2 = 3

P命令打印变量值

(gdb) up

#1  0x0000000000401580 in main () at test.cpp:8

8               swap(3,4);

(gdb) p a

$3 = 3

(gdb) p b

$4 = 4

up选择上一个栈帧进行查看

退出gdb

(gdb) q

A debugging session is active.

 

        Inferior 1 [process 5004] will be killed.

 

Quit anyway? (y or n) y

用指针实现变量交换,修改代码

#include<stdio.h>

void swap(int *a,int *b) {

         int t=*a;*a=*b;*b=t;

}

int main()

{

         int a=3,b=4;

         swap(&a,&b);

         printf("%d %d\n",a,b);

         return 0;

}

gdb调试

C:\Users\laugo>gcc test.cpp -o test -Wall -g

 

C:\Users\laugo>gdb test.exe

GNU gdb (GDB) 7.8.1

Copyright (C) 2014 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "x86_64-w64-mingw32".

Type "show configuration" for configuration details.

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>.

Find the GDB manual and other documentation resources online at:

<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".

Type "apropos word" to search for commands related to "word"...

Reading symbols from test.exe...done.

(gdb) l

1       #include<stdio.h>

2       void swap(int *a,int *b) {

3               int t=*a;*a=*b;*b=t;

4       }

5       int main()

6       {

7               int a=3,b=4;

8               swap(&a,&b);

9               printf("%d %d\n",a,b);

10              return 0;

(gdb) b 4

Breakpoint 1 at 0x40155e: file test.cpp, line 4.

(gdb) r

Starting program: C:\Users\laugo\test.exe

[New Thread 8928.0x280c]

[New Thread 8928.0x14c8]

 

Breakpoint 1, swap (a=0x62fe4c, b=0x62fe48) at test.cpp:4

4       }

(gdb) bt

#0  swap (a=0x62fe4c, b=0x62fe48) at test.cpp:4

#1  0x000000000040158f in main () at test.cpp:8

(gdb) p a

$1 = (int *) 0x62fe4c

(gdb) p b

$2 = (int *) 0x62fe48

(gdb) p *a

$3 = 4

(gdb) p *b

$4 = 3

(gdb) up

#1  0x000000000040158f in main () at test.cpp:8

8               swap(&a,&b);

(gdb) p a

$5 = 4

(gdb) p b

$6 = 3

(gdb) p &a

$7 = (int *) 0x62fe4c

(gdb) p &b

$8 = (int *) 0x62fe48

#0栈的ab*a*b分别对应#1栈的&a&bab

地址0x开头的整数以16进制表示,

(int *)指针*a代表a指向的变量,而不仅是a指向变量所拥有的值

(*a)++代表a指的值+1,不是*a++++运算符优先级高)

错误2:

不要滥用指针

递归

递归

#include<stdio.h>

int f(int n){

         return (n==0)?1:f(n-1)*n;

}

int main(){

        

         printf("%d",f(3));

         return 0;

}

三元运算符?:a?b:c的含义是:如果a为真,则b,如果a为假,则c

gdb调试

C:\Users\laugo>gcc test.cpp -o test -Wall -g

 

C:\Users\laugo>gdb test.exe

Reading symbols from test.exe...done.

(gdb) l

1       #include<stdio.h>

2       int f(int n) {

3               return n==0?1:f(n-1)*n;

4       }

5       int main()

6       {

7               printf("%d\n",f(3));

8               return 0;

9       }

b f设置断点,除了按行号设置,也可以用函数名,断点将设置在函数开头

(gdb) b f

Breakpoint 1 at 0x40153b: file test.cpp, line 3.

(gdb) r

Starting program: C:\Users\laugo\test.exe

[New Thread 10056.0x21f0]

[New Thread 10056.0x1fb0]

 

Breakpoint 1, f (n=3) at test.cpp:3

3               return n==0?1:f(n-1)*n;

(gdb) s

 

Breakpoint 1, f (n=2) at test.cpp:3

3               return n==0?1:f(n-1)*n;

(gdb) s

 

Breakpoint 1, f (n=1) at test.cpp:3

3               return n==0?1:f(n-1)*n;

(gdb)

 

Breakpoint 1, f (n=0) at test.cpp:3

3               return n==0?1:f(n-1)*n;

(gdb)

4       }

第一次断点处传入参数n=3,接下来调用f(3-1)…直到0

查看调用栈

(gdb) bt

#0  f (n=0) at test.cpp:4

#1  0x000000000040154e in f (n=1) at test.cpp:3

#2  0x000000000040154e in f (n=2) at test.cpp:3

#3  0x000000000040154e in f (n=3) at test.cpp:3

#4  0x0000000000401576 in main () at test.cpp:7

(gdb) s

4       }

(gdb) bt

#0  f (n=1) at test.cpp:4

#1  0x000000000040154e in f (n=2) at test.cpp:3

#2  0x000000000040154e in f (n=3) at test.cpp:3

#3  0x0000000000401576 in main () at test.cpp:7

(gdb) s

4       }

(gdb) bt

#0  f (n=2) at test.cpp:4

#1  0x000000000040154e in f (n=3) at test.cpp:3

#2  0x0000000000401576 in main () at test.cpp:7

(gdb) s

4       }

(gdb) bt

#0  f (n=3) at test.cpp:4

#1  0x0000000000401576 in main () at test.cpp:7

(gdb) s

6

main () at test.cpp:8

8               return 0;

(gdb) bt

#0  main () at test.cpp:8

每次s,都会有一层递归调用终止,

每次递归调用多一个栈帧,调用结束少一个栈帧,直到返回main

函数调用都是建立新栈帧,传递参数,修改当前代码行。在函数体执行完后删除栈帧,处理返回值,修改当前代码行

 

段错误与栈溢出

测试f(100000000),不是整数溢出,而是没有输出

-g编译后gdb载入r执行,报错

(gdb) r

Starting program: C:\Users\laugo\test.exe

[New Thread 5044.0x26e4]

[New Thread 5044.0x383c]

 

Program received signal SIGSEGV, Segmentation fault.段错误

0x0000000000401549 in f (n=99956661) at test.cpp:3

3               return n==0?1:f(n-1)*n;

可执行文件:UNIX/LinuxELF格式,DOSCOFF格式,WindowsPE格式(由COFF扩充)这些格式都有段概念:Segmentation

段是二进制文件内的区域,所有某种特定类型信息被保存在里面。Size程序可以得到可执行文件中各个段的大小

C:\Users\laugo>size test.exe

   text    data     bss     dec     hex filename

  10404    2344    2656   15404    3c2c test.exe

由正文段(存储命令)、数据段(存储已初始化的全局变量)、bbs段组成(存储未赋值的全局变量所需空间),总共15404,十六进制表示为3c2c

不能越界访问,否则段错误

每次调用往调用栈加一个栈帧,所以越界了,又称栈溢出(Stack Overflow

Linux栈空间的大小由系统命令ulimit指定,

windows栈大小存储在可执行文件(连接程序ld),gcc在编译时可以指定可执行文件的栈大小:

-Wl,--stack=<byte count>

-Wl,--stack=16777216

把较大的数组放在main外,因为局部变量也放在堆栈段,栈溢出不一定是递归太多,也也可能是局部变量开太大

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值