C从汇编代码角度理解类和对象提供的命名空间地址偏移
假设对某一个小问题的代码有两个写法A,B,其中A使用较简单的语法,需要写较多的代码;B使用较抽象的语法,只需要写较少的代码。B使用的语法是对A使用的语法的抽象,称为语法糖,其中的对应关系由编译器来实现,如:int arr[5] = {1,2,3}; arr[2] = 33; *(arr+2) = 33; // 可以理解为前一种写法是后一种写法的语法糖
我们知道,从机器语言到汇编语言到高级语言,实现了逐层抽象,中间的翻译工作由翻译程序汇编器和编译器来实现。高层抽象可以理解为低层抽象的语法糖。
一定程度上,面向对象可以理解为面向过程的语法糖,或者说,两者只是组织代码的方式发生了变化而已,数据可以聚合到结构体或类,而函数以结构体为参数,或函数隐含一个this指针做参数,指向类对象(当然,这里是就抽象和封装而言,类还提供更多的功能,如RAII,继承,多态,RTTI等):#include using namespace std; // 角色c1 长方形接口定义者,可放到头文件,使用文件来区分模块 struct CRect{ int lenth; int width; }; int area(struct CRect *cr); // 角色c2 长方形接口实现者,可以放到实现文件 int area(struct CRect *cr){ return cr->lenth * cr->width; } // 角色c3 长方形接口使用者,可以放到使用文件 int calc() { struct CRect cr; cr.lenth = 0x20; cr.width = 0x10; int ar = area(&cr); return ar; } // 角色cpp1 长方形接口定义者,可放到头文件,使用文件和类来区分模块 class CppRect{ public: int lenth; int width; int area(); }; // 角色cpp2 长方形接口实现者,可以放到实现文件 int CppRect::area(){ return lenth * width; } // 角色cpp3 长方形接口使用者,可以放到使用文件 int calc2() { CppRect cr; cr.lenth = 0x20; cr.width = 0x10; int ar = cr.area(); return ar; } int main() { printf("using struct: rect"s area is %d ", calc()); printf("using class: rect"s area is %d ", calc2()); while(1); return 0; } /* using struct: rect"s area is 512 using class: rect"s area is 512 */
从以上实例可见,数据的聚合没有区别,都是由结构体变量或类对象提供一个基地址,而数据成员提供一个相对于基地址的偏移。处理结构体的全局函数以结构体变量或指针为参数,而类的成员函数的调用则是以类对象来修饰,提供一个隐含的this指针做参数,指向类对象。当然,类为类成员函数提供了一个命名空间。
从下面汇编代码可见,两者没有太大的区别:19: struct CRect cr; 20: cr.lenth = 0x20; 004015D8 mov dword ptr [ebp-8],20h 21: cr.width = 0x10; 004015DF mov dword ptr [ebp-4],10h 22: int ar = area(&cr); 004015E6 lea eax,[ebp-8] // 全局函数参数地址放到寄存器 004015E9 push eax 004015EA call @ILT+650(area) (0040128f) // 全局函数调用 004015EF add esp,4 004015F2 mov dword ptr [ebp-0Ch],eax 23: return ar; 004015F5 mov eax,dword ptr [ebp-0Ch] 24: } 42: CppRect cr; 43: cr.lenth = 0x20; 00401678 mov dword ptr [ebp-8],20h 44: cr.width = 0x10; 0040167F mov dword ptr [ebp-4],10h 45: int ar = cr.area(); 00401686 lea ecx,[ebp-8] // 成员函数this指针放到寄存器 00401689 call @ILT+75(CppRect::area) (00401050) // 成员函数调用 0040168E mov dword ptr [ebp-0Ch],eax 46: return ar; 00401691 mov eax,dword ptr [ebp-0Ch] 47: }
如果结构体变量或类对象定义在全局区,汇编后,其实也看不到什么对象的影子了:49: struct CRect g_rect; 50: void test(){ 004016E0 push ebp 004016E1 mov ebp,esp 004016E3 sub esp,40h 004016E6 push ebx 004016E7 push esi 004016E8 push edi 004016E9 lea edi,[ebp-40h] 004016EC mov ecx,10h 004016F1 mov eax,0CCCCCCCCh 004016F6 rep stos dword ptr [edi] 51: g_rect.length = 0x20; 004016F8 mov dword ptr [g_rect (0047cde8)],20h // 全局区结构体数据成员赋值 52: g_rect.width = 0x10; 00401702 mov dword ptr [g_rect+4 (0047cdec)],10h 53: printf("using struct to defile a globle:%d ",area(&g_rect)); 0040170C push offset g_rect (0047cde8) 00401711 call @ILT+660(area) (00401299) 00401716 add esp,4 00401719 push eax 0040171A push offset string "using struct to defile a globle:"... (0046f01c) 0040171F call printf (00420860) 00401724 add esp,8 54: } // …… 56: CppRect g_cppRect; 57: void test2(){ 00401750 push ebp 00401751 mov ebp,esp 00401753 sub esp,40h 00401756 push ebx 00401757 push esi 00401758 push edi 00401759 lea edi,[ebp-40h] 0040175C mov ecx,10h 00401761 mov eax,0CCCCCCCCh 00401766 rep stos dword ptr [edi] 58: g_cppRect.length = 0x20; 00401768 mov dword ptr [g_cppRect (0047cdf0)],20h // 全局区对象数据成员赋值 59: g_cppRect.width = 0x10; 00401772 mov dword ptr [g_cppRect+4 (0047cdf4)],10h 60: printf("using class to defile a globle:%d ",g_cppRect.area()); 0040177C mov ecx,offset g_cppRect (0047cdf0) 00401781 call @ILT+75(CppRect::area) (00401050) 00401786 push eax 00401787 push offset string "using class to defile a globle:%"... (0046f048) 0040178C call printf (00420860) 00401791 add esp,8 61: } 004017A6 int 3
以下代码可见类对象相对于类成员的基地址及命名空间功能:#include using namespace std; class Person{ public: int m_id; int m_age; int m_height; void display(){ //类名给成员函数提供了一个命名空间 cout<m_id = 40; // 此处的成员只提供基于p到m_id的偏移:p+sizeof(int)*2 p->m_age = 50;// 此处的成员只提供基于p到m_id的偏移:p+sizeof(int)*3 cout<display(); // 编译器通过类对象名给函数提供了一个隐含的this指针参数 // this指针等于p的值,其成员变量的地址是相对于p处的偏移 } void display(){ // 并没有命名冲突 ; } void test2(){ // 不使用实例来也可以调用成员函数 cout<<"test2: "; Person person; person.m_id = 10; person.m_age = 20; person.m_height = 30; typedef void (*funcP)(); funcP fp = NULL; void (Person::*fpp)() = &Person::display; // 成员函数指针 memcpy(&fp,&fpp,sizeof(fpp)); __asm lea ecx, person; // x86编译器将this指针存放在寄存器ecx中 fp(); // 不通过实例person间接调用Person::diaplay() (person.*fpp)(); // 通过函数指针调用Person::diaplay() } int main() { test(); test2(); while(1); return 0; } /* 10,20,30 10,40,50 10,40,50 40,50,1245000 */
从以上可知,通过对象指针访问数据成员时,数据成员名提供的是一个相对于基地址的偏移的功能。成员函数定义时,类名给成员函数提供了一个命名空间,类对象调用成员函数时,编译器通过类对象名给函数提供了一个隐含的this指针参数。
-End-
依然是性价比小米再次拉低智能手表门槛现在热门的智能产品除了智能音箱感觉就是智能手表了,当年智能手环火爆的时候也有入手,虽然性价比高,但是屏幕小功能少也限制手环的市场。智能手表功能全可玩性高,缺点就是价格略高,基本上都
OPPOReno7标准版竟使用金属中框,OPPO良心发现?刚发布的OPPOReno7系列有三款,分别为OPPORenoSE,OPPOReno7和OPPOReno7Pro。毫无疑问,其中OPPOReno7就是标准版的定位了。不过这一次令我意
4K电竞显示器正确打开方式,极致光线追踪,少不了旗舰卡RTX3090前言初中时候,在广州儿童公园看到有大神用60寸以上的大屏幕玩街机游戏。虽说是1991年那种像素块的游戏,不过超大屏幕的带来的震撼感还是让几乎全公园的人都停步。相信游戏玩家都有一个梦
你认为最耐用国产手机是什么?最近几年,国产手机进步非常大,在质量和创新上与国际品牌三星苹果差距越来越小。特别是华为和小米,在某些方面甚至有超过三星苹果的趋势,因此越来越多的国人开始选择国产手机。所谓十年磨一剑
液晶电视摔坏了怎么办?液晶显示器简称LCD(LiquidCrystalDisplay),采用一种介于固态和液态之间的物质,具有规则性分子排列的有机化合物,加热呈现透明状的液体状态,冷却后出现结晶颗粒的混
有哪些是你们买完后悔的手机?1华为P10华为P10经过闪存门后的成为了一款被人们诟病的手机,也被称为一部坑人的手机。P10宣传了很久,造势很大,大家对P10的期待度也非常高,可从内存缩水屏幕没有疏油层到闪存门
鸿蒙系统要想快速推广,提高市场占有率,你有啥好建议?我先来说一说,仅是抛砖引玉。我的手机是vivo,想抢华为nova8很久了,想着华为CPU配华为鸿蒙,哪是绝配,可是,抢了很多次都没有结果,可想而知,华为被人制裁有多憋屈。明明自己是
小米汽车总部宣告落户北京经开区,整车工厂计划年产30万辆记者伍洋宇编辑11月27日,北京亦庄微信公众号发布消息,北京经济技术开发区管委会与小米科技进行签约,正式宣告小米汽车落户北京经开区。小米汽车项目更多细节释出。据悉,小米汽车总部基地
北京开放自动驾驶出行商业化试点价格如何确定?来源北京日报手机下单,自动驾驶乘用车为您服务。25日,北京正式开放国内首个自动驾驶出行服务商业化试点,率先在经济技术开发区60平方公里范围投入不超过100辆自动驾驶车辆开展商业化服
元宇宙的主力部队华为入局元宇宙了。在很多人纷纷打假的时候,诸多科技巨头正在紧锣密鼓的布局元宇宙。真有意思,很多人都不知道元宇宙是啥就先义愤填膺的去打假了,看来被梦想窒息之流坑怕了。这些年毕竟被各种
36氪首发布局配药机器人,博为医疗完成数千万元A轮融资近日,36氪获悉,智慧医疗领域中智能化静脉药物调配系统研发公司博为医疗(全称为深圳市博为医疗机器人有限公司)完成数千万元A轮融资,由某战略投资方投资。此前,公司曾获天使轮和A轮融资