九、结构体—函数之间数据传递
C语言中函数参数传递
C语⾔中函数参数传递C语⾔中函数参数传递的三种⽅式(1)值传递,就是把你的变量的值传递给函数的形式参数,实际就是⽤变量的值来新⽣成⼀个形式参数,因⽽在函数⾥对形参的改变不会影响到函数外的变量的值。
(2)地址传递,就是把变量的地址赋给函数⾥形式参数的指针,使指针指向真实的变量的地址,因为对指针所指地址的内容的改变能反映到函数外,能改变函数外的变量的值。
(3)引⽤传递,实际是通过指针来实现的,能达到使⽤的效果如传址,可是使⽤⽅式如传值。
说⼏点建议:如果传值的话,会⽣成新的对象,花费时间和空间,⽽在退出函数的时候,⼜会销毁该对象,花费时间和空间。
因⽽如果int,char等固有类型,⽽是你⾃⼰定义的类或结构等,都建议传指针或引⽤,因为他们不会创建新的对象。
例1:下⾯这段代码的输出结果为:#include<stdio.h>void change(int*a, int&b, int c){c=*a;b=30;*a=20;}int main ( ){int a=10, b=20, c=30;change(&a,b,c);printf(“%d,%d,%d,”,a,b,c);return 0;}结果:20 30 30解析:1,指针传参 -> 将变量的地址直接传⼊函数,函数中可以对其值进⾏修改。
2,引⽤传参 -> 将变量的引⽤传⼊函数,效果和指针相同,同样函数中可以对其值进⾏修改。
3,值传参 -> 在传参过程中,⾸先将c的值复制给函数c变量,然后在函数中修改的即是函数的c变量,然后函数返回时,系统⾃动释放变量c。
⽽对main函数的c没有影响。
例2:#include<stdio.h>void myswap(int x, int y){int t;t=x;x=y;y=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(a,b); //作为对⽐,直接交换两个整数,显然不⾏printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}#include<stdio.h>void myswap(int *p1, int *p2){int t;t=*p1;*p1=*p2;*p2=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(&a,&b); //交换两个整数的地址printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}#include<stdio.h>void myswap(int &x, int &y){int t;t=x;x=y;y=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(a,b); //直接以变量a和b作为实参交换printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}第⼀个的运⾏结果:输⼊2 3,输出2 3第⼆个的运⾏结果:输⼊2 3,输出3 2第三个的运⾏结果:输⼊2 3,输出3 2解析:在第⼀个程序中,传值不成功的原因是指在形参上改变了数值,没有在实参上改变数值。
函数参数传递的两种方式
函数参数传递的两种方式在编写函数时,函数的参数传递方式是非常关键的。
函数的参数传递方式分为值传递和引用传递两种方式。
本文将会对这两种方式进行详细介绍和比较。
一、值传递值传递是指函数参数在传递的过程中,实参的值被复制到函数的形参中进行传递。
这意味着,当函数在处理参数时,它只能获取到形参的副本,而无法改变原来实参的值。
因此,变量的值无法在函数内部改变。
当函数执行完毕后,这些参数的值将会被销毁。
在C语言中,基本数据类型、结构体以及数组等都属于值类型,它们默认的传递方式是值传递。
下面的例子演示了值传递的过程:```cvoid swap(int a, int b){int temp = a;a = b;b = temp;}上面的代码中,函数swap()将x和y的值交换,但由于传递的是参数的副本,函数并不能够改变x和y的值。
因此,最终输出结果还是原来的值。
优点:1. 值传递的方法比较简单。
2. 对于实参的值不需要保护的情况下,使用值传递往往会更高效。
3. 传递副本避免了不必要的占内存,对于内存的规划来说,非常有利。
1. 对于一些要修改实参值的情况,值传递无法完成。
2. 对于大数据的复制,值传递可能会导致内存的占用率上升。
二、引用传递引用传递是指将实参的地址传给函数,以便在函数内部操作实参的值。
因为直接传入的是实参的地址,所以函数内部对参数的操作直接就是作用于实参本身,可以改变参数的值。
引用传递使用&符号来传递地址。
在C语言中,使用指针类型传递参数可以实现引用传递。
下面的例子演示了引用传递的过程:上面的代码中,函数swap()使用指针类型参数,对于参数进行了引用传递,可以更改实参的值。
因此,在函数执行完成后,x和y的值已经被交换。
2. 可以省去大量复制数据的开销,加快函数的调用速度。
3. 引用传递可以使得函数在执行过程中,能够访问外部变量的地址,比较灵活。
2. 引用传递也会占用一些额外的空间。
综合比较在选择传递方式时,应该根据函数的实际需求进行选择。
C语言关于结构体做参数传递
C语言关于结构体做参数传递C语言中结构体类型可以作为函数的参数进行传递。
通过结构体做参数传递,可以将一个或多个相关的数据封装到一个结构体中,然后直接传递结构体作为参数,从而简化函数调用过程,提高程序可读性和维护性。
在C语言中,结构体是一种用户自定义的数据类型,它由多个不同类型的成员组成。
结构体的声明通常放在函数外部,以便于多个函数共享同一种数据结构。
下面我们来探讨几种关于结构体做参数传递的常见使用方式以及它们的特点。
首先是结构体值传递。
结构体作为函数参数传递时,会将结构体的每个成员的值拷贝一份传递给函数参数,函数内部对参数进行的修改不会影响外部的原结构体。
下面是一个示例:```c#include <stdio.h>struct Personchar name[20];int age;};void displayPerson(struct Person p)printf("Name: %s\n", );printf("Age: %d\n", p.age);int maistruct Person p1 = {"Tom", 20};displayPerson(p1);return 0;```在这个示例中,displayPerson函数接受一个Person类型的参数p,并打印其成员name和age。
当displayPerson函数调用时,会将p1的值拷贝到参数p,所以在函数内部修改p的值不会影响p1的值。
输出结果为:```Name: TomAge: 20```其次是结构体指针传递。
结构体指针可以在函数内部直接修改结构体的值,从而实现对原结构体的修改。
下面是一个示例:```c#include <stdio.h>struct Pointint x;int y;};void movePoint(struct Point *p, int dx, int dy)p->x += dx;p->y += dy;int maistruct Point p = {1, 2};movePoint(&p, 3, 4);printf("New coordinates: (%d, %d)\n", p.x, p.y);return 0;```在这个示例中,movePoint函数接受一个Point类型的指针p,并将x和y成员分别增加dx和dy的值。
c语言函数参数传递方式
c语言函数参数传递方式C语言是一种广泛使用的编程语言,函数参数传递方式是C语言中非常重要的概念之一。
函数参数传递方式可以分为按值传递、按址传递和按引用传递三种方式。
本文将针对这三种方式进行详细讲解。
一、按值传递按值传递是指在函数调用时,将实际参数的值复制给形式参数,函数内部对形参的修改不会影响到实际参数的值。
这种方式适用于参数较少、参数值不需要在函数内部被修改的情况。
在按值传递的方式下,函数在栈内存中为形参分配空间,并将实参的值复制到形参中。
函数执行结束后,栈内存中的形参被销毁,不会影响到实参的值。
二、按址传递按址传递是指在函数调用时,将实际参数的地址传递给形式参数,函数内部通过指针对实参进行操作,可以修改实参的值。
这种方式适用于需要在函数内部修改实参值的情况。
在按址传递的方式下,函数在栈内存中为形参分配空间,并将实参的地址传递给形参。
函数内部通过指针对实参进行操作,修改实参的值。
由于传递的是地址,所以函数内部对形参的修改会影响到实参。
三、按引用传递按引用传递是C++中的特性,其本质是通过指针来实现的。
在C语言中,可以通过传递指针的方式来模拟按引用传递。
按引用传递的特点是可以修改实参的值,并且不需要像按址传递那样使用指针操作。
在按引用传递的方式下,函数在栈内存中为形参分配空间,并将实参的地址传递给形参。
函数内部通过引用的方式操作形参,可以直接修改实参的值。
由于传递的是地址,所以函数内部对形参的修改会影响到实参。
需要注意的是,按引用传递需要使用指针来实现。
在函数调用时,需要将实参的地址传递给形参,即传递一个指向实参的指针。
函数内部通过解引用指针来操作实参,可以达到修改实参的目的。
总结:C语言中的函数参数传递方式包括按值传递、按址传递和按引用传递三种方式。
按值传递适用于参数较少、参数值不需要在函数内部被修改的情况;按址传递适用于需要在函数内部修改实参值的情况;按引用传递需要使用指针来实现,通过传递实参的地址来实现对实参的修改。
c语言结构体数组传参
c语言结构体数组传参C语言结构体数组传参结构体数组是C语言中常用的数据类型,它能够存储一组相关的数据,并且可以方便地传递给函数进行处理。
本篇文章将从介绍结构体数组的定义开始,逐步详解结构体数组的传参方法和使用技巧。
一、结构体数组的定义结构体是一种用户自定义的数据类型,它能够将多个不同类型的变量组合成为一个整体。
结构体数组是由多个相同类型的结构体变量组成的数组。
在C语言中,可以通过如下方式定义一个结构体数组:cstruct student {int id;char name[20];int age;} stuArr[100];上述代码定义了一个结构体数组stuArr,其中每个元素都是一个包含id、name和age三个成员变量的结构体变量。
stuArr的长度为100,即可以存储100个学生的信息。
二、结构体数组的传参结构体数组作为函数的参数时,可以通过值传递或指针传递的方式进行。
1. 值传递方式值传递是将结构体数组的副本传递给函数,函数对副本的修改不会影响到原数组。
cvoid printStudents(struct student arr[], int n) {for (int i = 0; i < n; i++) {printf("ID: d\n", arr[i].id);printf("Name: s\n", arr[i].name);printf("Age: d\n", arr[i].age);}}int main() {struct student stuArr[3] = {{1001, "Tom", 18},{1002, "Jerry", 19},{1003, "Alice", 20}};printStudents(stuArr, 3);return 0;}上述代码中,printStudents函数接收一个结构体数组和数组的长度作为参数,遍历数组并打印每个学生的信息。
c语言结构体作为函数参数
c语言结构体作为函数参数一、引言C语言中,结构体是一种非常重要的数据类型,可以将多个不同类型的变量封装在一个结构体中,方便管理和使用。
在函数中使用结构体作为参数,可以将多个相关变量作为一个整体传递给函数,提高程序的可读性和可维护性。
本文将详细介绍C语言中如何使用结构体作为函数参数,并且提供一个全面详细的函数示例。
二、结构体作为函数参数1. 声明结构体类型在使用结构体作为函数参数之前,需要先声明一个结构体类型。
例如,我们定义一个名为Person的结构体类型,包含姓名、年龄和性别三个成员变量:```typedef struct {char name[20];int age;char sex;} Person;```2. 定义函数并传递结构体参数接下来我们定义一个名为printPerson的函数,并将Person类型的变量作为参数传递给它:```void printPerson(Person p) {printf("Name: %s\n", );printf("Age: %d\n", p.age);printf("Sex: %c\n", p.sex);}```在这个函数中,我们首先输出了传入的Person类型变量p中的姓名、年龄和性别三个成员变量。
3. 调用函数并传递结构体参数现在我们可以调用printPerson函数,并传递一个Person类型的变量作为参数:```int main() {Person p = {"Tom", 20, 'M'};printPerson(p);return 0;}```在main函数中,我们定义了一个名为p的Person类型变量,并初始化了它的姓名、年龄和性别三个成员变量。
接下来,我们调用printPerson函数,并将p作为参数传递给它。
4. 输出结果最终程序会输出以下结果:```Name: TomAge: 20Sex: M```三、结构体指针作为函数参数除了使用结构体变量作为函数参数之外,还可以使用结构体指针作为函数参数。
计算机专业研究生复试-C语言程序设计面试简答题
C语言程序设计1.简述C语⾔采取了哪些措施提⾔执⾔效率●使⽤指针:有些程序⽤其他语⽤也可以实现,但C能够更有效地实现;有些程序⽤法⽤其它语⽤实现,如直接访问硬件,但C却可以。
正因为指针可以拥有类似于汇编的寻址⽤式,所以可以使程序更⽤效。
●使⽤宏函数:宏函数仅仅作为预先写好的代码嵌⽤到当前程序,不会产⽤函数调⽤,所以仅仅是占⽤了空间,⽤使程序可以⽤效运⽤。
在频繁调⽤同⽤个宏函数的时候,该现象尤其突出。
函数和宏函数的区别就在于,宏函数占⽤了⽤量的空间,⽤函数占⽤了时间。
●使⽤位操作:位操作可以减少除法和取模的运算。
在计算机程序中数据的位是可以操作的最⽤数据单位,理论上可以⽤"位运算"来完成所有的运算和操作。
灵活的位操作可以有效地提⽤程序运⽤的效率。
●将汇编指令嵌⽤到C 语⽤程序中,汇编语⽤是效率最⽤的计算机语⽤,因此在C语⽤程序中嵌⽤汇编,从⽤充分利⽤⽤级语⽤和汇编语⽤各⽤的特点。
●系统调用:在C语⽤程序中可以调⽤操作系统级的API,从⽤提⽤程序的运⽤效率。
●条件编译:C语⽤源程序中加上条件编译,让编译器只对满⽤条件的代码进⽤编译,将不满⽤条件的代码舍弃,可以减少编译及执行程序代码量。
●循环嵌套中将较长循环设为内置循环,较短循环设为外置循环,以减少cpu跨切循环层的次数,提⽤程序的运⽤效率。
(操作系统页⽤置换相关,减少页⽤置换次数)●其它诸如寄存器变量、联合体、编译器优化等手段提⽤执⽤效率。
2.if…else和switch区别总结:都是条件选中语句。
但switch语句只能取代if语句的一部分功能。
●比较的范围不同:if 语句可做各种关系比较(只要是boolean 表达式都可以用if 判断)switch语句只能做等式比较,即只能对基本类型进行数值比较。
(switch只能做几个数据类型的等式比较,实现非等式效率低,)switch之后括号内的表达式只能是整型(byte、short、char和int)、枚举型或字符型表达式,不能是长整型或其他任何类型。
c语言函数调用时参数传递方式的有哪几种,分别简述他们的传递方式
c语言函数调用时参数传递方式的有哪几种,分别简述他们的传
递方式
C语言函数调用时参数的传递方式主要有以下几种:
1. 值传递:函数调用时,将实际参数的值复制给形式参数,函数内部对形式参数进行修改不会影响实际参数的值。
这是最常见的参数传递方式。
2. 引用传递:通过传递变量的指针作为参数,函数内部可以直接通过指针访问和修改实际参数的值。
这种方式可以实现在函数内部改变实参的值。
3. 地址传递:传递变量的地址作为参数,在函数内部通过指针来访问和修改实际参数的值。
和引用传递类似,通过地址传递也可以改变实参的值。
4. 数组传递:将数组的首地址作为参数传递给函数,函数内部可以通过指针来访问和修改数组的元素。
5. 结构体传递:将整个结构体作为参数传递给函数,在函数内部可以直接访问和修改结构体中的成员。
需要注意的是,C语言中的参数传递都是按值传递的,包括引
用传递和地址传递。
所谓按值传递,是指在函数调用时将实参的值复制给形参,函数内部对形参的操作不会影响到实参的值。
但是通过引用传递和地址传递,可以通过指针来访问和修改实参的值,使得函数可以改变实参的值。
第九章 结构体和共用体
– 【例9.5】用指向结构体变量的指针变量引用结构体变量
14
• (2)结构体数组的指针
– 结构体指针具有同其他类型指针一样的特征和使用方 法。结构体指针变量也可以指向结构体数组。同样结 构体指针加减运算也遵照指针计算规则。例如,结构 体指针变量加1的结果是指向结构体数组的下一个元素。 结构体指针变量的地址值的增量取决于所指向的结构 体类型变量所占存储空间的字节数。 – 【例9.6】有4名学生,每个学生的属性包括学号、姓名、 成绩,要求通过指针方法找出成绩最高者的姓名和成 绩。 – 程序设计分析:将学生信息存入数组中,通过指针依 次访问每一个学生信息,比较其分数,从而求出获得 最高分学生在数组中的位置。
– 在结构体变量定义之后对结构体变量赋值时可 以采用各成员赋值,用输入语句或赋值语句来 完成。 – 【例9.2】在定义后对结构体变量赋初值。 – 【例9.3】有两条记录,记录包括数量(num)和 价钱(price),编写一程序完成总价钱的计算。
11
9.2 结构体数组与结构体指针
9.2.1结构体数组
5
• ④结构体类型的定义只说明了该类型的构成形式,系统并 不为其分配内存空间,编译系统仅给变量分配内存空间 • ⑤结构体成员的类型也可以是另外一个结构体类型。如: 1. struct date 2. { int year; 3. int month; 4. int day; 5. }; 6. struct student 7. { 8. int num; 9. char name[20]; 10. char sex; 11. struct date birthday; 12. int score[5]; 13.}; • 结构体成员的类型如果是另外一个结构体类型,同样必须 遵守先定义后使用的原则。如上例中,先定义struct date类 6 型,再定义struct student类型。
函数间数据传递方式
函数之间的逻辑联系是通过函数调用实现的, 在函数调用过程中,函数之间的数据联系则是 由函数间的数据传递建立的。 在C语言中,函数间数据传递有3种方式: ★参数传递 ★函数返回值 ★全局变量
பைடு நூலகம் 参数传递
函数间参数传递是指把变量或常量的有关信息 传递给函数,而传递的过程发生在函数调用期 间 函数间参数传递的数据类型可以是变量、数组、 指针、结构以及函数等 在函数调用时,实参和形参不是靠名称相同来 传递数据,而是靠对应位置来传递数据,这就 要求形参和实参在数据类型、个数和顺序上一 一对应 函数间传递参数方式有两种:传值与传址
参数传递
例题:最长相似字符串 输入2个字符串(每个字符串长度不超过 255),从第一个字符开始对比两个字符串, 检查下两个字符串相同位置上的连续字符串最 长长度是多少。例如字符串“student”和字 符串“study”,两者最长相似字符串长度为4, 字符串“welcome to usaco”和字符串 “forcomedto usaco”的最长相似字符串长度 是8
参数传递
使用数组名作函数参数,即使在定义形参的时候定义 为“int a[10]”类似的形式,但编译系统并不单独开 辟存储区存放形参数组,实参不是把值一个一个传给 形参,而是把实参数组的起始地址传给形参。这样, 形参数组与实参数组共享存储空间,当形参数组中元 素值发生变化时,实参数组的值也会发生变化。 编译系统不对形参数组大小作检查,因此一维数组可 以不指定大小,数组名后直接跟个空的方括号,数组 的元素个数可以用其他参数来传递。 二维数组名也可以做实参和形参,在定义形参时,可 以将二维数组的第一维大小省略。例如: int max(int b[][5])
判断题-170道
试题标题C语言中,字符数据用格式说明"%c"来输出,字符串数据用格式说明"%s"来输出。
C语言中,关键字及编译预处理命令用小写字母书写。
二元运算符均可以和赋值运算符构成复合的赋值运算符。
C语言采用解释方式将源程序转换为二进制的目标代码。
用C语言可直接进行位运算和取地址等运算,因此C语言是一种低级语言。
C程序是由函数构成的,每一个函数完成相对独立的功能。
被main函数调用的函数只能是库函数。
在C源程序中,之间的注释信息也被编译,但对程序运行运果不发生影响。
C语言中没有依存于硬件的I/O语句。
C语言中,不能在变量名、函数名、关键字中插入空格和空行。
Vasual c++6.0中可以使用“//”和 “/* */”注释程序,注释部分不编译。
字符常数和字符串常数的存储是相同的,都是以字符的ASCII码值进行存放。
整型数据在内存中占2个字节。
实型数据在内存中占4个字节。
字符型数据在内存中占1个字节。
存储字符串常数时所占内存空间是不固定。
赋值表达式c=b=a=3是错误的。
在进行逻辑运算时,非0整型值表示“真”。
条件运算符“?:”的优先级高于赋值运算符。
C程序中的一个变量可以被定义为两个或多个不同的类型。
字符常量是用单撇号括来的一个字符,这个字符可以是任意的。
按现代风格设计的C程序中,main中对函数声明的信息与定义此函数时第一行的信息完全相同。
C语言中,格式说明"%10.4f"中10表示数据输出的最小宽度,4表示小数位数。
C语言中,为了区别一般的变量,符号常量必须用大写字母表示。
转义字符是一种常量。
表达式1/4+2.75的值是3。
执行语句++i ; i=3;后变量i的值为4。
程序中的“=”是赋值号,与数学中等号的功能相同。
C语言中,在进行混合运算时,数据类型由高级向低级转换。
将C程序编译成目标文件,其扩展名为EXE。
整数在存储时不会产生误差,实数在存储时也不会产生误差。
第9章 结构体、共用体和枚举类型
9.1 结构体类型与结构体变量 9.2 结构体数组 9.3 向函数传递结构体型数据 9.4 链表 9.5 共用体 9.6 枚举类型 9.7 用户自定义类型
9.1 结构体类型与结构体变量
• 结构体类型的定义
–概述:
• 建立单链表的主要步骤为:
– 生成只含有头结点的空链表; – 然后读取数据信息,生成新结点,将数据存放于新结点中, 插入新结点到单链表中 – 重复第二步,直到输入结束。 根据新结点插入到链表的位置的不同,建立链表的方式, 分为在表尾插入的方法和在表头插入的方法。 •表尾插入: 例9.6 •表头插入: 例9.7
• 共用体变量的定义
–用已定义的共用体类型去定义共用体变量 –定义共用体类型的同时定义变量: –定义无名共用体类型的同时定义变量:
• 共用体变量的引用
–引用方式:共用体变量名.成员名 共用体变量名. 共用体变量名 –说明: 对于共用体变量,只能引用它的成员,而不能引用整个 共用体变量。若共用体的成员是构造类型的,则需要逐级引 用至最低级的成员。 –能通过定义指向共用体的指针变量来引用共用体变量。
在实际应用中,一组相关的数据可能是不同类型的。C语 言提供了包含不同类型成员的类型来定义这样的数据,这 种类型就是结构体类型 结构体类型。 结构体类型
– 定义形式:
struct 结构体名 类型名1 成员名1 {类型名1 成员名1; 类型名2 成员名2 类型名2 成员名2; …… 类型名n 成员名n 类型名n 成员名n; };
• 举例
例9.11 上一页
9.6 枚举类型
• 枚举类型的定义–定义来自式:enum 枚举类型名{枚举常量1,枚举常量2,……,枚举常量n}; •
函数传参 结构体
函数传参结构体函数传参是编程中常用的一种技术手段,它通过将数据或对象作为参数传递给函数,以实现不同模块之间的数据交互和功能调用。
而结构体是一种用户自定义的数据类型,它可以将多个不同类型的数据组合到一起,形成一个更为复杂的数据结构。
在编程中,我们经常会遇到需要同时传递多个数据给函数的情况,而使用结构体作为函数的参数可以很好地解决这个问题。
通过定义一个包含多个字段的结构体,我们可以将相关的数据打包在一起,方便地传递给函数。
这样不仅能够提高代码的可读性和可维护性,还能够减少函数参数的数量,简化函数的调用方式。
在使用函数传参结构体的过程中,我们需要注意以下几点:需要在函数的参数列表中声明结构体类型的参数。
例如,我们可以定义一个名为"Student"的结构体,用于表示学生的信息,包括姓名、年龄和成绩等字段。
然后,在函数中可以通过该结构体类型的参数来获取和操作学生的信息。
需要在函数调用时将结构体的实例作为参数传递给函数。
我们可以先创建一个学生对象,并为其各个字段赋值,然后将该对象作为参数传递给函数。
函数在接收到参数后,可以通过访问结构体实例的字段来获取和处理相应的数据。
结构体作为函数参数时,可以使用值传递或者指针传递的方式。
值传递是指将结构体的副本传递给函数,函数对该副本进行操作不会影响原始数据;而指针传递是指将结构体的地址传递给函数,函数可以直接修改原始数据。
根据具体的需求和性能要求,我们可以选择适合的传递方式。
在实际应用中,函数传参结构体可以帮助我们实现诸如学生管理系统、员工薪资计算、图像处理等功能。
举例来说,假设我们需要编写一个程序来统计学生的平均成绩。
我们可以定义一个包含学生姓名和成绩的结构体,并编写一个函数来计算平均成绩。
函数的参数就可以是一个包含多个学生信息的结构体数组。
在函数内部,我们可以遍历结构体数组,累加每个学生的成绩,最后除以学生人数得到平均成绩。
除了传递结构体作为参数外,我们还可以在结构体中定义函数,这样的函数称为结构体的方法。
c语言中结构体的定义和引用方式
c语言中结构体的定义和引用方式C语言中结构体的定义和引用方式概念介绍在C语言中,结构体(struct)是一种用户自定义的数据类型,允许我们将不同类型的数据组合到一个单独的结构中。
通过结构体,我们可以创建一个包含多个不同数据类型的集合,从而方便地组织和管理大量数据。
定义结构体要定义一个结构体,我们需要使用关键字struct,加上结构体的标识符(名称),以及花括号{}来定义结构体的成员变量。
每个成员变量都有自己的数据类型和标识符。
下面是一个表示学生的结构体定义:```struct Student {int studentID;char name[20];int age;```在上面的例子中,我们使用了结构体标识符“Student”来表示学生,并定义了三个成员变量:学生ID(studentID),尊称(name)和芳龄(age)。
引用结构体变量一旦我们定义了结构体,就可以声明结构体变量并使用它们来存储和访问成员变量的值。
结构体变量的声明方式类似于普通变量的声明,但需要在结构体标识符前面加上关键字struct。
我们可以声明一个名为"student1"的结构体变量来表示一个学生:```struct Student student1;```我们可以使用点操作符(.)来访问结构体变量中的成员变量。
要为"student1"的学生ID赋值,我们可以使用以下语句:```student1.studentID = 123456;同样,我们也可以通过点操作符来访问和修改其他成员变量。
结构体作为函数参数和返回值结构体可以作为函数的参数和返回值。
这使得我们能够更方便地在不同的函数之间传递和操作结构体数据。
如果我们希望在函数中修改结构体变量的值,则需要将结构体变量作为指针传递给函数。
这样,函数就可以通过指针来访问和修改结构体的成员变量。
下面是一个函数,用于显示学生的信息:```cvoid displayStudent(struct Student *student) {printf("学生ID:%d\n", student->studentID);printf("尊称:%s\n", student->name);printf("芳龄:%d\n", student->age);}```在调用函数时,我们可以传入指向结构体的指针:```cdisplayStudent(&student1);```为了方便起见,我们还可以定义一个返回结构体的函数,以便在其他地方使用。
c语言程序设计第14章 结构体共用体和用户定义类型-海贝
float score; char addr[30];
} stu1,stu2;
3. 直接定义结构体变量
struct {
类型标识符 成员名; 类型标识符 成员名;
……………. } 变量名表列;
4. 先用typedef说明一个结构体类型名, 再用新类型名来定义变量
typedef struct {
char name[12]; char sex; struct date birthday; float sc[4]; } STREC; STREC std, pers[3], *pstd
stu[0] stu[1]
结构体数组初始化
结顺分构序行体初初数始始组化化引:: 用 stru{}例s例scssst引tttrttrruruuusu用s{}{}s{}c[{}ciccitcc3ttnntss;uthh;rrtt]ttt方uudaauu;ssasccrrest[[ntiiiccicciciitcciug式ttntunnnnnnsnunuhhhhh]]uhhedtedttttt==atdtmaaaasdaa;ex:emaaat{{rrrrrearrennnnun;n;gggn{{gnsnsnnsuuuetnsduteeet……eeeteaaa[mmmaem;;;xx;x2xmmm结mnss;;;;;;……0;;ttteeeue]u构[[[;[[}}[2222,,]体000]{{0==]]]……]{;;;{;数111{s{{……10t0111组r0011c0s}},,,p001t名“““,,u{{y,,,LWL“““[……([1LWiLisa下u]tGiin……u.auYaGg标[angY0an}}gaLen]a}}g]n+.L”i.nn;;”g成n+,”i,a‘””‘n;F,mM,,‘员”‘‘’FMM,e,‘’1’,名M,,”91’’1Z,,}9912’h,;,}902a}},0o;,}D, a”);
C语言中函数之间地址传递方式
C语言中函数之间地址传递方式导语:C语言中函数之间的数据传递方式有值传递、引用传递、地址传递。
下面就由小编为大家介绍一下C语言中函数之间地址传递方式,欢迎大家阅读!1函数之间数据传递方式分类C语言程序是由函数组成的。
设计C语言程序时,通常将一个大的程序按功能分成若干个较小的模块,每个模块编写成结构清晰、接口简单、容易理解的程序段,即函数。
这种方法可建立公用模块,消除重复工作,提高程序开发效率。
[1]从函数的形式来看,函数可分为无参函数和有参函数。
在调用有参函数时,主调函数与被调用函数之间有数据传递,也就是说,主调函数可以将数据传递给被调函数使用,被调函数中的数据也可以带回来给主调函数使用。
值传递:数据只能从实参单向传递给形参,称为“按值”传递。
当基本类型变量作为实参时,在函数调用过程中,形参和实参占据不同的存储空间,形参的改变对实参的值不产生任何影响引用传递:使实参和形参共用一个地址,即所谓“引用传递”。
这种传递方式,无论对哪个变量进行修改,都是对同一地址内存空间的内容进行修改,其实参变量与它的引用即形参变量,总是具有相同的值。
例如程序:2函数之间的地址传递2.1形参为指针变量时函数之间的数据传递。
如果函数的形参为指针类型时,对应的实参类型必须与形参的基类型相同。
例如程序:调用swap函数,用指针传递的方式交换主函数中变量x和y中的数据。
函数之间值的传递是单向传递,也就是说函数只能通过实参把值传递给形参,若形参值改变,对实参不会产生影响;把数据从被调函数返回到主调函数的唯一途径就是通过return语句,且只能返回一个数据。
若是采用以上通过传递地址值的方式,可以在被调用函数中对主调函数中的变量进行引用,通过改变形参的值而让实参的值得到相应改变,这样就可以实现把多个数据从被调用函数返回到主调用函数。
2.2一维数组名作实参时函数之间的数据传递。
函数之间在进行数据传递时,数组元素可以作为实参传递给形参,这时的数组元素与普通变量一样,这种传递实际上就是值的传递。
c语言函数传输传递的三种方式(值、指针、引用)
c语⾔函数传输传递的三种⽅式(值、指针、引⽤)本⽂摘⾃《彻底搞定c指针》⼀、三道考题开讲之前,我先请你做三道题⽬。
(嘿嘿,得先把你的头脑搞昏才⾏……唉呀,谁扔我鸡蛋?)考题⼀,程序代码如下:void Exchg1(int x, int y){int tmp;tmp = x;x = y;y = tmp;printf("x = %d, y = %d\n", x, y);}main(){int a = 4,b = 6;Exchg1(a, b);printf("a = %d, b = %d\n", a, b);return(0);}输出的结果为: 20x = ____, y=____.a = ____, b=____.问下划线的部分应是什么,请完成。
考题⼆,程序代码如下:void Exchg2(int *px, int *py){int tmp = *px;*px = *py;*py = tmp;printf("*px = %d, *py = %d.\n", *px, *py);}main(){int a = 4;int b = 6;Exchg2(&a, &b);printf("a = %d, b = %d.\n", a, b);return(0);}输出的结果为为:*px=____, *py=____.a=____, b=____.问下划线的部分应是什么,请完成。
考题三,程序代码如下:void Exchg3(int &x, int &y)21{int tmp = x;x = y;y = tmp;printf("x = %d,y = %d\n", x, y);}main(){int a = 4;int b = 6;Exchg3(a, b);printf("a = %d, b = %d\n", a, b);return(0);}输出的结果为:x=____, y=____.a=____, b=____.问下划线的部分应是什么,请完成。
《C语言程序设计》复习大纲
《C语言程序设计》复习大纲(此课程为考查课,开卷考试)第一章C语言程序设计初步1、从编写一个C程序到完成运行一般经过以下几个步骤:编辑、编译、连接、执行。
2、标识符命名规则:由英文字母、数字和下划线组成;长度不大于32个字符;必须以英文字母或下划线开头;不能和关键字重名;区分大小写;命名要“见名知意”。
3、C语言的输入输出功能是由C的库函数提供的。
4、C程序是由函数组成的,一个完整的程序必须有一个main函数,即主函数,程序总是从main 函数开始执行的,不论main函数在整个程序的什么位置。
第二章程序设计基础知识1、算法的特性:有穷性、确定性、有效性、有零个或多个输入、有一个或多个输出。
第三章C数据及其表达式1、C语言中基本数据类型有:整型、字符型、实型、枚举型。
2、C语言中有四种基本常量:整形常量、实型常量、字符常量和字符串常量。
两种特殊常量:转义字符常量和符号常量。
3、一个整型常量以2个字节存储,一个长整型数占4个字节。
4、字符常量用单引号括起来,字符串常量用双引号括起来;每个字符串结尾都有一个字符串结束标志‟\0‟,‟a‟和”a”最大的区别就是”a”实际上是两个字符:‟a‟和‟\0‟; 而‟a‟只是一个字符本身。
5、逗号表达式的一般形式为:表达式1,表达式2;逗号表达式的计算过程是:先求解表达式1,再求解表达式2。
整个逗号表达式的值是表达式2的值。
6、++i,--i i的值先增1(或减1),然后再参与其他运算;i++,i-- 先参与其它运算,然后使i值增1(或减1)第四章顺序结构程序设计1、程序分为顺序结构、选择结构或分支结构、循环结构。
2、C语言的语句大体上分为五种类型:1、函数调用语句;2、表达式语句;3、控制语句;4、空语句;5、复合语句。
3、语句在最后必须出现分号,分号是语句中不可缺少的一部分。
4、putchar(c)的功能是向屏幕输出一个字符;printf(“格式化字符串”,输出项表)的功能是按用户指定的格式,把指定的数据显示到屏幕上。
C语言反汇编-函数与结构体
C语⾔反汇编-函数与结构体反汇编(Disassembly) 即把⽬标⼆进制机器码转为汇编代码的过程,该技术常⽤于软件破解、外挂技术、病毒分析、逆向⼯程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解⾼级语⾔代码都有相当⼤的帮助,软件⼀切神秘的运⾏机制全在反汇编代码⾥⾯。
函数是任何⼀个⾼级语⾔中必须要存在的⼀个东西,使⽤函数式编程可以让程序可读性更⾼,充分发挥了模块化设计思想的精髓,今天我将带⼤家⼀起来探索函数的实现机理,探索编译器到底是如何对函数这个关键字进⾏实现的,从⽽更好地理解编译⾏为。
先来研究函数,函数是任何⼀门编程语⾔中都存在的关键字,使⽤函数式编程可以让程序可读性更⾼,充分发挥模块化设计思想的精髓,⽽函数传参的底层实现就是通过堆栈来实现的,⾸先我们来理解⼀下堆栈.当有参函数被执⾏时,通常会根据不同的调⽤约定来对参数进⾏压栈存储以STDcall约定为例,栈的调⽤原则是先进后出,最先被push到堆栈中的数据会被最后释放出来,⽽CPU中有两个寄存器专门⽤于维护堆栈的变化,ESP栈顶寄存器,EBP栈底寄存器(基址),这两个寄存器就像是好基友,两个寄存器相互配合,来让堆栈有条不乱.栈帧:就是ESP -> EBP 之间的空间,通常是调⽤函数时,函数的参数,从⼀个函数切换到另⼀个函数上,栈帧也会发⽣变化,当函数调⽤结束后,则需要平栈帧,不然会发⽣访问冲突,平栈帧的过程都是有编译器来解决的。
逆向分析函数实现机制函数与堆栈的基础: 下⾯⼀个简单的函数调⽤案例,我们来看看汇编格式是怎样的.#include <stdio.h>int VoidFunction(){printf("hello lyshark\n");return 0;}int main(int argc, char* argv[]){VoidFunction();return 0;}编译上⾯的这段代码,⾸先我们找到main函数的位置,然后会看到call 0x4110E1这条汇编指令就是在调⽤VoidFunction()函数,观察函数能发现函数下⽅并没有add esp,xxx这样的指令,则说明平栈操作是在函数的内部完成的,我们直接跟进去看看函数内部到底做了什么见不得⼈的事情.0041142C | 8DBD 40FFFFFF | lea edi,dword ptr ss:[ebp-0xC0] |00411432 | B9 30000000 | mov ecx,0x30 |00411437 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |0041143C | F3:AB | rep stosd |0041143E | E8 9EFCFFFF | call 0x4110E1 | 调⽤VoidFunction()00411443 | 33C0 | xor eax,eax | main.c:1300411445 | 5F | pop edi | main.c:14, edi:"閉\n"00411446 | 5E | pop esi | esi:"閉\n"00411447 | 5B | pop ebx |此时我们直接跟进call 0x4110E1这个函数中,分析函数内部是如何平栈的,进⼊函数以后⾸先使⽤push ebp保存当前EBP指针位置,然后调⽤mov ebp,esp这条指令来将当前的栈帧付给EBP也就是当基址使⽤,sub esp,0xC0则是分配局部变量,接着是push ebx,esi,edi则是因为我们需要⽤到这⼏个寄存器所以应该提前将原始值保存起来,最后⽤完了就需要pip edi,esi,ebx恢复这些寄存器的原始状态,并执⾏add esp,0xC0对局部变量进⾏恢复,最后mov esp,ebp还原到原始的栈顶指针位置,⾸尾呼应.004113C0 | 55 | push ebp | 保存栈底指针 ebp004113C1 | 8BEC | mov ebp,esp | 将当前栈指针给ebp004113C3 | 81EC C0000000 | sub esp,0xC0 | 抬⾼栈顶esp,开辟局部空间004113C9 | 53 | push ebx | 保存 ebx004113CA | 56 | push esi | 保存 esi004113CB | 57 | push edi | 保存 edi004113CC | 8DBD 40FFFFFF | lea edi,dword ptr ss:[ebp-0xC0] | 取出次函数可⽤栈空间⾸地址004113D2 | B9 30000000 | mov ecx,0x30 | ecx:"閉\n", 30:'0'004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |004113DC | F3:AB | rep stosd |004113DE | 8BF4 | mov esi,esp | main.c:5004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] | 调⽤printf004113EB | 83C4 04 | add esp,0x4 | 降低栈顶esp,释放printf局部空间004113EE | 3BF4 | cmp esi,esp | 检测堆栈是否平衡,ebp!=esp则不平衡004113F0 | E8 46FDFFFF | call 0x41113B | 堆栈检测函数:检测平衡,不平衡则报错004113F5 | 33C0 | xor eax,eax | main.c:6004113F7 | 5F | pop edi | 还原寄存器edi004113F8 | 5E | pop esi | 还原寄存器esi004113F9 | 5B | pop ebx | 还原寄存器ebx004113FA | 81C4 C0000000 | add esp,0xC0 | 恢复esp,还原局部变量00411400 | 3BEC | cmp ebp,esp |00411402 | E8 34FDFFFF | call 0x41113B |00411407 | 8BE5 | mov esp,ebp | 还原原始的ebp指针00411409 | 5D | pop ebp |0041140A | C3 | ret |上⽅的代码其实默认⾛的是STDCALL的调⽤约定,⼀般情况下在Win32环境默认遵循的就是STDCALL,⽽在Win64环境下使⽤的则是FastCALL,在Linux系统上则遵循SystemV的约定,这⾥我整理了他们之间的异同点.这⾥我们来演⽰CDECL的调⽤约定,其实我们使⽤的Printf()函数就是在遵循__cdecl()约定,由于Printf函数可以有多个参数传递,所以只能使⽤__cdecl()约定来传递参数,该约定的典型特点就是平栈不在被调⽤函数内部完成,⽽是在外部通过使⽤⼀条add esp,0x4这种⽅式来平栈的.004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] |004113EB | 83C4 04 | add esp,0x4 | 平栈004113EE | 3BF4 | cmp esi,esp |004113F0 | E8 46FDFFFF | call 0x41113B |004113F5 | 8BF4 | mov esi,esp | main.c:6004113F7 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113FC | FF15 14914100 | call dword ptr ds:[<&printf>] | 平栈00411402 | 83C4 04 | add esp,0x4 |在使⽤Release版对其进⾏优化的话,此段代码将会采取复写传播优化,将每次参数平衡的操作进⾏归并,⼀次性平衡栈顶指针esp,从⽽可以⼤⼤的提⾼程序的执⾏效率,汇编代码如下:004113E0 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113E5 | FF15 14914100 | call dword ptr ds:[<&printf>] |004113F7 | 68 58584100 | push consoleapplication1.415858 | 415858:"hello lyshark\n"004113FC | FF15 14914100 | call dword ptr ds:[<&printf>] |00411402 | 83C4 04 | add esp,0x8 | ⼀次性平栈加上0x8,平了前⾯的2个push通过以上分析发现_cdecl与_stdcall两者只在参数平衡上有所不同,其余部分都⼀样,但经过优化后_cdecl调⽤⽅式的函数在同⼀作⽤域内多次使⽤,会在效率上⽐_stdcall髙,这是因为_cdecl可以使⽤复写传播,⽽_stdcall的平栈都是在函数内部完成的,⽆法使⽤复写传播这种优化⽅式.除了前⾯的两种调⽤约定以外_fastcall调⽤⽅式的效率最髙,其他两种调⽤⽅式都是通过栈传递参数,唯独_fastcall可以利⽤寄存器传递参数,但由于寄存器数⽬很少,⽽参数相⽐可以很多,只能量⼒⽽⾏,故在Windows环境中_fastcall的调⽤⽅式只使⽤了ECX和EDX寄存器,分别传递第1个参数和第2个参数,其余参数传递则依然使⽤堆栈传递.#include <stdio.h>void _fastcall VoidFunction(int x,int y,int z,int a){printf("%d%d%d%d\n", x, y, z, a);}int main(int argc, char* argv[]){VoidFunction(1,2,3,4);return 0;}反汇编后观察代码发现call 0x4110E6就是在调⽤我们的VoidFunction()函数在调⽤之前分别将参数压⼊了不同的寄存器和堆栈中,接着我们继续跟进到call函数内部,看它是如何取出参数的.0041145E | 6A 04 | push 0x4 | 第四个参数使⽤堆栈传递00411460 | 6A 03 | push 0x3 | 第三个参数使⽤堆栈传递00411462 | BA 02000000 | mov edx,0x2 | 第⼆个参数使⽤edx传递00411467 | B9 01000000 | mov ecx,0x1 | 第⼀个参数使⽤ecx传递0041146C | E8 75FCFFFF | call 0x4110E6 |00411471 | 33C0 | xor eax,eax | main.c:11进⼊call 0x4110E6这个函数中,观察发现⾸先会通过mov指令将前两个参数提取出来,然后再从第四个参数开始依次将参数取出来并压栈,最后让Printf函数成功调⽤到.004113E0 | 8955 EC | mov dword ptr ss:[ebp-0x14],edx | edx => 提取出第⼆个参数004113E3 | 894D F8 | mov dword ptr ss:[ebp-0x8],ecx | ecx => 提取出第⼀个参数004113E6 | 8BF4 | mov esi,esp | main.c:5004113E8 | 8B45 0C | mov eax,dword ptr ss:[ebp+0xC] | 保存第四个参数004113EB | 50 | push eax |004113EC | 8B4D 08 | mov ecx,dword ptr ss:[ebp+0x8] | 保存第三个参数004113EF | 51 | push ecx |004113F0 | 8B55 EC | mov edx,dword ptr ss:[ebp-0x14] | 保存第⼆个参数004113F3 | 52 | push edx |004113F4 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] | 保存第⼀个参数004113F7 | 50 | push eax |004113F8 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d%d%d%d\n"004113FD | FF15 14914100 | call dword ptr ds:[<&printf>] |00411403 | 83C4 14 | add esp,0x14 | 平栈定义并使⽤有参函数: 我们给函数传递些参数,然后分析其反汇编代码,观察代码的展⽰形式.#include <stdio.h>int Function(int x,float y,double z){if (x = 100){x = x + 100;y = y + 100;z = z + 100;}return (x);}int main(int argc, char* argv[]){int ret = 0;ret = Function(100, 2.5, 10.245);printf("返回值: %d\n", ret);return 0;}下⽅的反汇编代码就是调⽤函数ret = Function()的过程,该过程中可看出压栈顺序遵循的是从后向前压⼊的.0041145E | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | main.c:1700411465 | 83EC 08 | sub esp,0x8 | main.c:1800411468 | F2:0F1005 70584100 | movsd xmm0,qword ptr ds:[<__real@40247d70a3d70a3d>] | 将10.245放⼊XMM0寄存器00411470 | F2:0F110424 | movsd qword ptr ss:[esp],xmm0 | 取出XMM0中内容,并放⼊堆栈00411475 | 51 | push ecx |00411476 | F3:0F1005 68584100 | movss xmm0,dword ptr ds:[<__real@40200000>] | 将2.5放⼊XMM00041147E | F3:0F110424 | movss dword ptr ss:[esp],xmm0 | 同理00411483 | 6A 64 | push 0x64 | 最后⼀个参数10000411485 | E8 51FDFFFF | call 0x4111DB | 调⽤Function函数0041148A | 83C4 10 | add esp,0x10 |0041148D | 8945 F8 | mov dword ptr ss:[ebp-0x8],eax | 将返回值压栈00411490 | 8BF4 | mov esi,esp | main.c:1900411492 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] |00411495 | 50 | push eax |00411496 | 68 58584100 | push consoleapplication1.415858 | 415858:"返回值: %d\n"0041149B | FF15 14914100 | call dword ptr ds:[<&printf>] | 输出结果004114A1 | 83C4 08 | add esp,0x8 |压栈完成以后我们可以继续跟进call 0x4111DB这个关键CALL,此处就是运算数据的关键函数,跟进去以后,可发现其对浮点数的运算,完全是依靠XMM寄存器实现的.004113F1 | 8945 08 | mov dword ptr ss:[ebp+0x8],eax |004113F4 | F3:0F1045 0C | movss xmm0,dword ptr ss:[ebp+0xC] | main.c:8004113F9 | F3:0F5805 8C584100 | addss xmm0,dword ptr ds:[<__real@42c80000>] |00411401 | F3:0F1145 0C | movss dword ptr ss:[ebp+0xC],xmm0 |00411406 | F2:0F1045 10 | movsd xmm0,qword ptr ss:[ebp+0x10] | main.c:90041140B | F2:0F5805 80584100 | addsd xmm0,qword ptr ds:[<__real@4059000000000000>] |00411413 | F2:0F1145 10 | movsd qword ptr ss:[ebp+0x10],xmm0 |00411418 | 8B45 08 | mov eax,dword ptr ss:[ebp+0x8] | main.c:11向函数传递数组/指针: 这⾥我们以⼀维数组为例,⼆维数组的传递其实和⼀维数组是相通的,只不过在寻址⽅式上要使⽤⼆维数组的寻址公式,此外传递数组其实本质上就是传递指针,所以数组与指针的传递⽅式也是相通的.#include <stdio.h>void Function(int Array[], int size){for (int i = 0; i<size; ++i){printf("输出元素: %d \n", Array[i]);}}int main(int argc, char* argv[]){int ary[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };Function(ary, 10);return 0;}以下代码就是Function(ary,10)函数的调⽤代码,⾸先压栈传递0A也就是10,接着传递ary⾸地址,最后调⽤call指令.004114B4 | 6A 0A | push 0xA | 10004114B6 | 8D45 D4 | lea eax,dword ptr ss:[ebp-0x2C] | ary ⾸地址004114B9 | 50 | push eax | push eax004114BA | E8 63FCFFFF | call 0x411122 | 调⽤Function()004114BF | 83C4 08 | add esp,0x8 | 堆栈修复函数中返回指针,其实就是返回⼀个内存地址,我们可以打印出这个内存地址具体的值,如下是⼀段测试代码,这⾥的原理于上⽅都是相通的,此处就不在浪费篇幅了.#include <stdio.h>int GetAddr(int number){int nAddr;nAddr = *(int*)(&number-1);return nAddr;}int main(int argc, char* argv[]){int address = 0;address = GetAddr(100);printf("%x\n",address);return 0;}函数的参数传递就到此结束了,其实其他的参数传递⽆外乎就是上⾯的这⼏种传递形式,只是在某些实现细节上略有差异,但⼤体上也就是这些东西,在真正的逆向过程中还需要考虑编译器的版本等具体细节,每⼀个编译器在实现参数传递上都略微不同,这也就是编译特性所影响的,我们应该灵活运⽤这些知识,才能更好地分析这些字节码.变量作⽤域解析接着我们来研究⼀下变量的作⽤域,在C语⾔中作⽤域可分为局部变量与全局变量,两种变量⼜分为静态变量和动态变量,接下来我们将通过反汇编学习研究他们之间的异同点.探索全局变量的奥秘: 全局变量与常量有很多相似的地⽅,两者都是在程序执⾏前就存在的,这是因为编译器在编译时就将其写⼊到的程序⽂件⾥,但是在PE⽂件中的只读数据节⾥,常量的节属性被修饰为不可写⼊,⽽全局变量和静态变量的属性为可读可写,PE⽂件加载器在加载可执⾏⽂件时,会率先装载这些常量与全局变量,然后才会运⾏程序⼊⼝代码,因此这些全局变量可以不受作⽤域的影响,在程序中的任何位置都可以被访问和使⽤,来看⼀段C代码:#include <stdio.h>int number1 = 1;int number2 = 2;int main(int argc, char* argv[]){scanf("%d", &number1);printf("您输⼊的数字: %d\n", number1);number2 = 100;return 0;}如下反汇编代码可以看出,全局变量的访问是直接通过⽴即数push consoleapplication1.415858访问的,此⽴即数是通过编译器编译时就写⼊到了程序中的,所以也就可以直接进⾏访问了.004113E0 | 68 00804100 | push <consoleapplication1._number1> | 此处的压栈参数就是全局变量004113E5 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d"004113EA | FF15 10914100 | call dword ptr ds:[<&scanf>] |004113F0 | 83C4 08 | add esp,0x8 | 保存第⼆个参数004113F3 | 3BF4 | cmp esi,esp |004113F5 | E8 41FDFFFF | call 0x41113B |004113FA | 8BF4 | mov esi,esp | main.c:9004113FC | A1 00804100 | mov eax,dword ptr ds:[<_number1>] |00411401 | 50 | push eax |00411402 | 68 5C584100 | push consoleapplication1.41585C | 41585C:"您输⼊的数字: %d\n"00411407 | FF15 18914100 | call dword ptr ds:[<&printf>] |0041140D | 83C4 08 | add esp,0x8 |00411410 | 3BF4 | cmp esi,esp |00411412 | E8 24FDFFFF | call 0x41113B |00411417 | C705 04804100 64000000 | mov dword ptr ds:[<_number2>],0x64 | main.c:11, 64:'d'00411421 | 33C0 | xor eax,eax | main.c:12探索局部变量的奥秘: 局部变量的访问是通过栈指针相对间接访问,也就是说局部变量是程序动态创建的,通常是调⽤某个函数或过程时动态⽣成的,局部变量作⽤域也仅限于函数内部,且其地址也是⼀个未知数,编译器⽆法预先计算.#include <stdio.h>int main(int argc, char* argv[]){int num1 = 0;int num2 = 1;scanf("%d", &num1);printf("%d", num1);num2 = 10;return 0;}反汇编代码,局部变量就是通过mov dword ptr ss:[ebp-0x8],0x0动态开辟的空间,其作⽤域就是在本函数退出时消亡.004113DE | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | 申请局部变量004113E5 | C745 EC 01000000 | mov dword ptr ss:[ebp-0x14],0x1 | main.c:6004113EC | 8BF4 | mov esi,esp | main.c:8004113EE | 8D45 F8 | lea eax,dword ptr ss:[ebp-0x8] |004113F1 | 50 | push eax |004113F2 | 68 58584100 | push consoleapplication1.415858 | 415858:"%d"004113F7 | FF15 10914100 | call dword ptr ds:[<&scanf>] |说到局部变量,不得不提起局部静态变量,局部静态变量的声明只需要使⽤static关键字声明,该变量⽐较特殊,他不会随作⽤域的结束⽽消亡,并且也是在未进⼊作⽤域之前就已经存在了,其实局部静态变量也是全局变量,只不过它的作⽤域被限制在了某⼀个函数内部⽽已,所以它本质上还是全局变量,来⼀段代码验证⼀下:#include <stdio.h>int main(int argc, char* argv[]){static int g_number = 0;for (int x = 0; x <= 10; x++){g_number = x;printf("输出: %d\n", g_number);}return 0;}观察这段反汇编代码,你能够清晰的看出,同样是使⽤mov eax,dword ptr ds:[<g_number>]从全局数据区取数据的,这说明局部变量声明为静态属性以后,就和全局变量变成了⼀家⼈了.004113DE | C745 F8 00000000 | mov dword ptr ss:[ebp-0x8],0x0 | main.c:7004113E5 | EB 09 | jmp 0x4113F0 |004113E7 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] |004113EA | 83C0 01 | add eax,0x1 |004113ED | 8945 F8 | mov dword ptr ss:[ebp-0x8],eax |004113F0 | 837D F8 0A | cmp dword ptr ss:[ebp-0x8],0xA | A:'\n'004113F4 | 7F 27 | jg 0x41141D |004113F6 | 8B45 F8 | mov eax,dword ptr ss:[ebp-0x8] | main.c:9004113F9 | A3 30814100 | mov dword ptr ds:[<g_number>],eax |004113FE | 8BF4 | mov esi,esp | main.c:1000411400 | A1 30814100 | mov eax,dword ptr ds:[<g_number>] | 与全局变量是⼀家⼈00411405 | 50 | push eax |00411406 | 68 58584100 | push consoleapplication1.415858 | 415858:"输出: %d\n"0041140B | FF15 14914100 | call dword ptr ds:[<&printf>] |00411411 | 83C4 08 | add esp,0x8 |00411414 | 3BF4 | cmp esi,esp |00411416 | E8 1BFDFFFF | call 0x411136 |0041141B | EB CA | jmp 0x4113E7 | main.c:110041141D | 33C0 | xor eax,eax | main.c:12探索堆变量的奥秘: 堆变量是最容易识别的⼀种变量类型,因为分配堆区的函数就⼏个calloc/malloc/new等,所以这类变量往往能被调试器直接补货到,这种变量同样属于局部变量的范畴,因为它也是通过函数动态申请的⼀段内存空间,这⾥只给出⼀个案例吧,反编译⼤家可以⾃⼰研究,这⼀个是很简单的了.#include <stdlib.h>#include <stdio.h>int main(int argc, char* argv[]){int *pMalloc = (int*)malloc(10);printf("变量地址: %x", pMalloc);free(pMalloc);return 0;}结构体与共⽤体针对C语⾔的反汇编,就剩⼀个结构体与共⽤体了,这⾥的内容⽐较少,我就不再新的⽂章⾥写了,直接在这⾥把它给写完,C语⾔的反汇编就到此结束。
c语言去耦合设计
c语言去耦合设计
在C语言中,可以通过函数参数传递、结构体和回调函数来实现去耦合设计。
1.函数参数传递:通过将需要传递的数据作为函数的参数,将相关的操作封装在函数中,可以降低代码之间的耦合度。
例如,在实现排序
算法时,可以将需要排序的数据作为参数传递给排序函数,排序函数内部实现排序算法,从而降低了代码之间的耦合度。
2.结构体:结构体是一种数据结构,可以用来封装多个变量,包括不同类型的数据。
通过将相关的数据封装在一个结构体中,可以将这些
数据作为一个整体进行处理,从而降低了代码之间的耦合度。
例如,在实现链表时,可以将节点的数据和指针封装在一个结构体中,从而降低了链表操作的耦合度。
3.回调函数:回调函数是一种函数指针,可以用来实现动态调用。
通过将需要执行的函数作为参数传递给另一个函数,可以让被调用函数
根据需要执行不同的操作,从而降低了代码之间的耦合度。
例如,在实现排序算法时,可以将比较函数作为参数传递给排序函数,排序函数根据比较函数的结果来决定如何排序,从而降低了代码之间的耦合度。
总之,通过以上方法可以实现C语言中的去耦合设计,使得代码更加模块化、可维护性和可扩展性。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
cout<<" "<<data.b<<" "<<&data.b<<endl;
cout<<" "<<data.c<<" "<<&data.c<<endl<<endl;
cout<<" "<<data.b<<" "<<&data.b<<endl;
cout<<" "<<data.c<<" "<<&data.c<<endl<<endl;
//利用结构体传递数据
out_s(data);
//利用结构体指针传递数据
out_p(point);
//利用结构体引用传递数据
out_r(rdata);
{
arr[0].a='M';
arr[1].a='N';
}
void main()
{
struct A data={'h',2,3.3};
struct A *point=&data;
struct A &rdata=data;
struct A array[2]={{'X',23,45.44},{'Y',99,100.23}};
cout<<"结构体存放内容"<<s.a<<" "<<(void *)&s.a<<endl;
cout<<" "<<s.b<<" "<<&s.b<<endl;
cout<<" "<<s.c<<" "<<&s.c<<endl<<endl;
}
void out_p(struct A *p) //利用结构体指针传递数据
{
cout<<"利用指针传递数据,形参p内存地址"<<&p<<endl;
cout<<"形参p存放内容"<<p<<endl<<endl;
}
void out_r(struct A &r) //利用结构体引用传递数据
{
r.a='-';
r.b=0;
r.c=0;
}
void out_a(struct A arr[])
【程序源代码】
#include<iostream.h>
struct A//构建结构体,sizeof(A)=16
{
char a;
int b;
double c;
};
void out_s(struct A s) //利用结构体变量传递数据
{
cout<<"利用结构体传递数据形参s内存地址"<<&s<<endl;
out_a
数组传递
0x00401038
0x00401039
0x0040103A
0x0040103B
【运行结果】
(别名)
存放内容
0x0018FEB4
char
s.a
h
形参使用结构体,在内存开辟空间,用后释放,所以与之后形参指针在内存分配上有重合
0x00018FEB7
0x0018FEB8
int
s.b
2
0x0018FEB9
0x0018FEBA
0x0018FEBB
0x0018FEBC
cout<<" "<<array[0].c<<" "<<&array[0].c<<endl;
cout<<" "<<array[1].a<<" "<<(void *)&array[1].a<<endl;
cout<<" "<<array[1].b<<" "<<&array[1].b<<endl;
cout<<" "<<array[1].c<<" "<<&array[1].c<<endl<<endl;
//各函数地址
cout<<"变量传递,函数地址"<<out_s<<endl;
cout<<"指针传递,函数地址"<<out_p<<endl;
cout<<"引用传递,函数地址"<<out_r<<endl;
cout<<"数组传递,函数地址"<<out_a<<endl;
}
【内存分配】
形参
内存地址
类型
变量名称
0x0018FF12
0x0018FF13
0x0018FF14
int
array[0].b
23
0x0018FF15
0x0018FF16
0x0018FF17
0x0018FF18
double
array[0].c
45.44
0x0018FF19
0x0018FF1A
0x0018FF1B
0x0018FF1C
0x0018FF1D
array[1].c
100.23
0x0018FF29
0x0018FF2A
0x0018FF2B
0x0018FF2C
0x0018FF2D
0x0018FF2E
0x0018FF2F
0x0018FF30
0x0018FF31
0x0018FF32
0x0018FF33
0x0018FF34
struct A *
ponit
//利用结构体引用传递数据
out_a(array);
cout<<"利用数组传递数据后"<<endl<<"结构体存放内容"<<array[0].a<<" "<<(void *)&array[0].a<<endl;
cout<<" "<<array[0].b<<" "<<&array[0].b<<endl;
上一节课,我们知道了什么是结构体类型,以及结构体变量占据内存的大小。既然结构体是一个数据类型,那么,除了用它定义结构体变量之外,我们还可以用它来定义结构体数组、结构体引用与指针、结构体函数……其实,只要你懂得整型数组、整型变量的引用与指针、以及返回值为整型的函数等使用,就很容易理解结构体类型类似的用法。
0x0018FF3E
0x0018FF3F
0x0018FF40
double
data.c
3.3
0x0018FF41
0x0018FF42
0x0018FF43
0x0018FF44
函数
内存地址
类型
函数名称
存放内容
0x00401028
void
out_r
引用传递
0x00401029
0x0040102A
0x0040102B
0x0040102C
0x0040102D
void
out_p
指针传递
0x0040102E
0x0040102F
0x00401030
0x00401031
0x00401032
void
out_s
变量传递
0x00401033
0x00401034
0x00401035
0x00401036
0x00401037
void
cout<<"结构体data指针所在内存地址"<<&point<<endl;
cout<<"结构体data指针所存放内容"<<point<<endl;
cout<<"结构体data存放内容"<<data.a<<" "<<(void *)&data.a<<endl; //字符型变量输出内存地址一般要进行数据类型强制
0x0018FF38
0x0018FF35
0x0018FF36