【精通高并发】深入理解C语言基础与汇编下的C语言(二)

举报
小明的混沌之路 发表于 2022/07/31 14:40:27 2022/07/31
【摘要】 本文如题,C语言基础部分不过多赘述,主要讲解结构体、指针的原理,并分析汇编下的C语言。

前言:📫 作者简介:小明java问道之路,专注于研究计算机底层,就职于金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的设计和架构📫 

🏆 Java领域优质创作者、阿里云专家博主、华为云专家🏆

🔥 如果此文还不错的话,还请👍关注点赞收藏三连支持👍一下博主哦

本文导读

本文如题,C语言基础部分不过多赘述,主要讲解结构体、指针和数组原理,并分析汇编下的C语言。Liunx内核和JNI都是通过C编写,这部分主要讲解通过操作指针和内存执行程序的思想,对后续Hotspot和java的api的理解有很重要的作用。

一、C语言基础

1.结构体应用

必须使用struct语句,struct语句定义了一个包含多个成员的数据类型

 struct tag {   // tag是结构体标签 
 	member-list // member-list 是标准的变量定义 
 	member-list 
 	... 
 } variable-list; // variable-list是结构变量,定义在结构体末尾,最后一个分号前,可以指定一个或多个结构体变量

结构体应用:介绍了如何声明(定义)结构体,初始化结构体变量(相当于new或者set对象),如何访问结构体成员(相当于访问对象属性) ,结构体作为函数参数和指向结构体的指针应用。

#include<stdio.h>
struct User { // 声明结构体 
	int age;
	char uaerName[50];
	int gender;
	cahr telePhone[20];
};
int main() {
	struct User user1;
	/**
	 * 初始化user1变量(相当于new或者set User对象) 
	 */ 
	struct User user3 = {18,"XiaoMing",1,"18812348888"};
	strcpy(user1.uaerName,"XiaoMing");
	strcpy(user1.telePhone,"18812348888");
	user1.gender=1;
	/**
	 * 访问结构体成员(相当于对象属性) 
	 */ 
	printf("user.getName() : %s",user1.uaerName); 
	printf("user.getAge() : %d",user1.age); 
	/**
	 * 结构体作为函数参数(相当于方法参数) 
	 */ 
	printUser(user1); 
	/**
	 * 指向结构体的指针 
	 */ 
	printUser1(&user1); 
}
void printUser(struct User user) {
	printf("user.getName() : %s",user.uaerName); 
} 
void printUser1(struct User *user) {
	printf("user.getName() : %s",user->uaerName); 
}

二、.从汇编的角度看结构体

从此段简单的代码分析,name和age地址相差8个字节,整好是一个整形4个字节+4个字节填充,我们将其反汇编,看下汇编代码的实现。

#include<stdio.h>
struct User { // 声明结构体 
	int age;
	char *name;
	long money;
};
int main() {
	struct User user = {18,"XiaoMing",10000};
	struct User *p = &user; 
	
	printf("user变量地址 : %p",&user); 
	printf("p指针访问值: %d",p->age); 
	printf("age的地址 : %p",&(p->age)); 
	printf("p指针访问值name: %s",p->name); 
	printf("name的地址: %p",&(p->name)); 
}
// user变量地址 : 000000000062FE30
// p指针访问值: 18
// age的地址 : 000000000062FE30
// p指针访问值name: XiaoMing
// name的地址: 000000000062FE38
// main方法汇编代码 
main: 
push   %rbp        // 开辟新的栈帧 
mov    %rsp,%rbp
sub    $0x40,%rsp  // 创造64byte的空间,需要注意下面存储的数据和内存对齐机制  
callq  0x4023b0 <__main>  
movl   $0x11,-0x20(%rbp)  // mov指令,将整型17(0x11,4个字节),字符串,长整型放到栈对应的地址中,相当于执行 struct User user = {17,"lisa",10000};
movl   $0x2710,-0x10(%rbp)

lea    0x28b5(%rip),%rax        # 0x404050 // 取17的地址放到rax寄存器中,然后保存在栈中,相当于执行 struct User *p = &user; 
mov    %rax,-0x18(%rbp)  // lea:取有效地址,mov:传送指令 
lea    -0x20(%rbp),%rax   
mov    %rax,-0x8(%rbp)

lea    -0x20(%rbp),%rax  //  取17的地址放到rax寄存器中,然后调用printf,相当于执行 printf("user变量地址 : %p",&user); 
mov    %rax,%rdx
lea    0x2899(%rip),%rcx        # 0x404055
callq  0x402dd0 <printf>

mov    -0x8(%rbp),%rax   //  取17的地址放到rax寄存器中,然后作为地址,将寻址的地址单元中的值放入eax寄存器 
mov    (%rax),%eax       //  (%rax)表示将rax寄存器中的值作为地址寻址放入eax寄存器中 
mov    %eax,%edx
lea    0x2897(%rip),%rcx        # 0x404067
callq  0x402dd0 <printf>

mov    -0x8(%rbp),%rax   // 将地址往前偏移8个字节单元地址的内容,放入rax寄存器,这个内容就是指向***的指针,由于64位机,此时整好是8个字节 
mov    %rax,%rdx
lea    0x2894(%rip),%rcx        # 0x404077
callq  0x402dd0 <printf>

mov    -0x8(%rbp),%rax
mov    0x8(%rax),%rax
mov    %rax,%rdx
lea    0x288c(%rip),%rcx        # 0x404086
callq  0x402dd0 <printf>

mov    -0x8(%rbp),%rax
add    $0x8,%rax
mov    %rax,%rdx
lea    0x2889(%rip),%rcx        # 0x40409a
callq  0x402dd0 <printf>

mov    $0x0,%eax
add    $0x40,%rsp  // 将地址值直接加8,相当于地址加8个字节 
pop    %rbp

lesve  // 清除栈帧并设置地址返回
retq         // ret:返回指令

三、指针原理

每个变量都会有一个内存地址,每个内存地址都可以使用&访问,他表示在内存中的地址。首先明确一个概念,指针就是一个变量,其值就是另一个变量的地址(内存位置的直接地址),所有使用的时候必须先声明。

	// type *varName; type是指针的基类型,必须是有效的数据类型 
	int *ip;    // 整形指针 
	double *dp; // 所有实际数据类型,都是内存地址16进制数 
	float *fp;
	char *cp; 

指针的应用:这里面p就是一个指针,与变量var的类型相同

#include<stdio.h>
int main() {
	int var = 20;
	int *p; 
	p = &var;
	printf("var变量的地址 : %p",&var); 
	printf("p指针的存储地址: %p",p); 
	printf("p指针访问的值: %d",*p); 
} 
// var变量的地址 : 000000000062FE44
// p指针的存储地址: 000000000062FE44
// p指针访问的值: 20 

1、从汇编的角度看指针

将上述代码反汇编之后的代码,作者为64位系统

main:
push   %rbp           // 开辟新的栈帧 
mov    %rsp,%rbp          
sub    $0x30,%rsp     // 在栈上开辟48(0x30)byte大小的空间 
callq  0x402120 <__main>
movl   $0x14,-0xc(%rbp) // 将4byte大小的20(0x14)放入栈中(rbp) 
lea    -0xc(%rbp),%rax  // 将20在栈中的地址取出,放入rax寄存器 
mov    %rax,-0x8(%rbp)  // 将rax寄存器,20的地址放入栈中,在64位系统中,地址大小是8byte  以上两行代码相当于 int *p; p=&var; 
lea    -0xc(%rbp),%rax  // 再次获取20的地址 

mov    %rax,%rdx     // 将20的地址从rax寄存器放入rdx寄存器中 
lea    0x2aa6(%rip),%rcx        # 0x404000
callq  0x402b38 <printf>    // 相当于printf("var变量的地址 : %p",&var); 

mov    -0x8(%rbp),%rax   // 将之前保存20的地址放入rax中 
mov    %rax,%rdx
lea    0x2aa6(%rip),%rcx        # 0x404013
callq  0x402b38 <printf>    // 相当于printf("p指针的存储地址: %p",p); 

mov    -0x8(%rbp),%rax   // 将之前保存20的地址放入rax中 
mov    (%rax),%eax       // 注意这里 (%rax),相当于将rax寄存器中的表露当做地址,去内存中获取对应地址的值,放入eax寄存器
						 // 20只有4byte(32位),所以不需要rax寄存器(64位) 
mov    %eax,%edx
lea    0x2aa6(%rip),%rcx        # 0x404027
callq  0x402b38 <printf>    // 相当于printf("p指针访问的值: %d",*p); 
mov    $0x0,%eax
add    $0x30,%rsp
pop    %rbp
retq   

这里我们总结,指针就是一个内存单元保存了一个地址,一般用&地址符,相当于 lea 指令,使用指针用 * 解地址符,相当于汇编代码中的 ()  例如 mov    (%rax),%eax,将之前的 lea 指令获取的地址信息作为访问,以获取地址响应的变量信息。

2、一些指针的基础应用 

通过指针访问数组,数组是连续的空间,指针中保存的是对应数据的地址,声明数组的时候就是默认新开辟连续的地址空间的第一个元素的地址,使用var[index] 等价于我们直接操作指针 * 引用获取元素;
指针数组就是保存元素地址(指针)的数组

#include<stdio.h>
int main() {
	/**
	 * 通过指针访问数组 
	 */
	int var[] = {1,2,3};
	printf("地址: %p",var); 
	printf("地址: %p",var+1); 
	printf("值: %d",*var); 
	printf("值: %d",*(var+1)); 	
	/**
	 * 指针数组 
	 */ 
	int i, *arr[3];
	for (i=0;i<3;i++) {
		arr[i] = &var[i];
	}
	for(i=0;i<3;i++){
		printf("var[%d] = %d ",i , *arr[i]); 	
	} 
} 
// 地址: 000000000062FE40
// 地址: 000000000062FE44
// 值: 1
// 值: 2
// var[0] = 1 var[1] = 2 var[2] = 3

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。