C++内存分区模型分析与实例以及扩展

简介: C++程序在执行时,将内存大方向划分为**5个区域**运行前:- 代码区:存放**函数体的二进制代码**,由操作系统进行管理的- 全局区(静态区):存放**全局变量和静态变量以及常量**- 常量区:**常量**存储在这里,不允许修改运行后:- 栈区:由编译器自动分配释放, 存放**函数的参数值**,**局部变量等**- 堆区:**由程序员分配和释放**,若程序员不释放,程序结束时由操作系统回收

内存分区模型

C++程序在执行时,将内存大方向划分为5个区域

运行前:

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区(静态区):存放全局变量和静态变量以及常量
  • 常量区:常量存储在这里,不允许修改

运行后:

  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:

不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程

程序运行前

分析

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

存放 CPU 执行的机器指令

代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

全局变量静态变量存放在此.

全局区还包含了常量区, 字符串常量和其他常量也存放在此.

==该区域的数据在程序结束后由操作系统释放==.

示例

原理:对比不同类型数据的地址区分区域划分。

//全局变量

intg_a=10;

intg_b=10;

//const修饰的全局变量:全局常量

constintc_g_a=10;

constintc_g_b=10;

intmain() {

   //局部变量

   inta=10;

   intb=10;

   cout<<"局部变量a地址为: "<< (int)&a<<endl;//(int)将地址信息转成10进制

   cout<<"局部变量b地址为: "<< (int)&b<<endl;

   cout<<"全局变量g_a地址为: "<<  (int)&g_a<<endl;

   cout<<"全局变量g_b地址为: "<<  (int)&g_b<<endl;

   //静态变量

   staticints_a=10;

   staticints_b=10;

   cout<<"静态变量s_a地址为: "<< (int)&s_a<<endl;

   cout<<"静态变量s_b地址为: "<< (int)&s_b<<endl;

   

   //常量

   //1,字符串常量

   cout<<"字符串常量地址为: "<< (int)&"hello world"<<endl;

   cout<<"字符串常量地址为: "<< (int)&"hello world1"<<endl;

   //2.1const修饰的全局变量:全局常量

   cout<<"全局常量c_g_a地址为: "<< (int)&c_g_a<<endl;

   cout<<"全局常量c_g_b地址为: "<< (int)&c_g_b<<endl;

   //2.2const修饰的局部变量

   constintc_l_a=10;

   constintc_l_b=10;

   cout<<"局部常量c_l_a地址为: "<< (int)&c_l_a<<endl;

   cout<<"局部常量c_l_b地址为: "<< (int)&c_l_b<<endl;

   system("pause");

   return0;

}

打印结果:

局部变量在一个段里;全局变量、静态变量、其他常量在另一个段里。

网络异常,图片无法展示
|

实例刨析:

网络异常,图片无法展示
|

局部变量:函数体内(栈区)
全局变量:函数体外
静态变量:函数体内(普通变量前加static)
常量:函数体内
1.字符串常量
2.const修饰的变量

(1)const修饰的全局变量:全局常量

(2)const修饰的局部变量不在全局区栈区

总结

  • C++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 全局区的常量区中存放 const修饰的全局常量  和 字符串常量

易混点

区分静态变量(static)与const修饰的局部变量

程序运行后

栈区分析

栈区:

由编译器自动分配释放, 存放函数的参数值,局部变量等

示例

int*func()

{

   inta=10;//局部变量存放在栈区,栈区的数据在函数执行完成后自动释放。

   return&a;//返回局部变量的地址

}

intmain() {

   int*p=func();

   cout<<*p<<endl;

   cout<<*p<<endl;

   system("pause");

   return0;

}

易错点

不要返回局部变量的地址,栈区开辟的数据由编译器自动释放,函数运行结束后函数内的局部变量被释放,将无法使用传回的函数体内的局部变量的地址!

注意:根据编译器不同,编译器有时会保留,但是注意不要这么做!

网络异常,图片无法展示
|

图片刨析:假设编译器只会保留一次函数体内局部变量的地址,即传出的地址只能调用一次。

如果假设成立,那么*func()的调用将不受次数限制,因为func()每次传回的都是最新的地址,而*p只能调用一次,因为*p经过了局部变量的存储,编译器保留了一次地址后将地址释放之后p地址将失效,无法继续访问。

堆区分析

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用new在堆区开辟内存

示例

int*func()

{

   int*a=newint(10);//利用new关键字将数据开辟到堆区

   returna;//只针的本质是局部变量,放在栈上,指针保存的数据是放在堆区的

}

intmain() {

   int*p=func();

   cout<<*p<<endl;

   cout<<*p<<endl;

   

   system("pause");

   return0;

}

注意点

int(10)是编译器在栈区暂时虚拟出的一块空间,上图代码int* a表示并给这块内存起名为a,类比与4.2.2构造函数中的匿名对象:Person(10)单独写就是匿名对象(等同于int(10)存于栈上,加上new关键字就存在与堆区了。),特点:当前行结束之后,马上析构,即系统立即回收掉匿名对象。

构造函数相关代码对比:

//1、构造函数分类

// 按照参数分类分为 有参和无参构造   无参又称为默认构造函数

// 按照类型分类分为 普通构造和拷贝构造

classPerson {

public:

   //无参(默认)构造函数

   Person() {

       cout<<"无参构造函数!"<<endl;

   }

   //有参构造函数

   Person(inta) {

       age=a;

       cout<<"有参构造函数!"<<endl;

   }

   //拷贝构造函数

   Person(constPerson&p) {

       age=p.age;

       cout<<"拷贝构造函数!"<<endl;

   }

   //析构函数

   ~Person() {

       cout<<"析构函数!"<<endl;

   }

public:

   intage;

};

//2、构造函数的调用

voidtest01() {

   //2.1  括号法(常用)

   Personp1;//调用无参构造函数,默认构造函数的调用

   Personp2(10);//有参构造函数

   Personp3(p2);//拷贝构造函数

   //注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明

   //Person p2()

   //2.2 显式法

   Personp2=Person(10); //相当于给匿名对象Person(10)起个名字叫p2

   Personp3=Person(p2);

   //Person(10)单独写就是匿名对象(等同于int(10)存于栈上),特点:当前行结束之后,马上析构,即系统立即回收掉匿名对象。

   //2.3 隐式转换法(简化的显示法)

   Personp4=10; // Person p4 = Person(10);

   Personp5=p4; // Person p5 = Person(p4);

   //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明

   //Person (p5);等同于Person p5;

}

intmain() {

   test01();

   system("pause");

   return0;

}

易错点

new int(10)返回的是地址,需要用指针接收!

总结:

堆区数据由程序员管理开辟和释放

堆区数据利用new关键字进行开辟内存

new操作符

C++中利用==new==操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 ==delete==

语法:new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针

示例1: 基本语法

int*func()

{

   int*a=newint(10);

   returna;

}

intmain() {

   int*p=func();

   cout<<*p<<endl;

   cout<<*p<<endl;

   //利用delete释放堆区数据

   deletep;

   //cout << *p << endl; //报错,释放的空间不可访问

   system("pause");

   return0;

}

示例2:开辟数组

//堆区开辟数组

intmain() {

   int*arr=newint[10];

   for (inti=0; i<10; i++)

   {

       arr[i] =i+100;

   }

   for (inti=0; i<10; i++)

   {

       cout<<arr[i] <<endl;

   }

   //释放数组 delete 后加 []

   delete[] arr;

   system("pause");

   return0;

}

易错点

释放数组要加中括号,如果不加中括号可能只会释放一个数据!

导图

网络异常,图片无法展示
|

扩展

C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。

每个区存储的内容如下:

1、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

2、堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。

3、静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。

4、常量区:常量存储在这里,不允许修改。

5、代码区:顾名思义,存放代码

C++中的new/delete、构造/析构函数、dynamic_cast分析

1,new 关键字和 malloc 函数区别(自己、功能、应用):

 1,new 关键字是 C++ 的一部分:

   1,如果是 C++ 编译器,则肯定可以用 new 申请堆空间内存;

 2,malloc 是由 C 库提供的函数:

   1,如果没有相应的库,malloc 将不能使用;

   2,有些特殊的嵌入式开发中,少了 C 库,则就不能动态内存分配;

 3,new 以具体类型为单位进行内存分配;

   1,面向对象中一般用 new,不用 malloc;

 4,malloc 以字节为单位进行内存分配;

 5,new 在申请内存空间时可进行初始化;

   1,触发构造函数调用;

 6,malloc 仅根据需要申请定量的内存空间;

   1,对象的创建只能用 new,malloc 不适合面向对象开发;

2,下面代码输出什么?为什么?见 new 和 malloc 的区别编程实验:

#include <iostream>

#include <string>

#include <cstdlib>

usingnamespacestd;

classTest

{

 int*mp; //为了说明 free() 可能造成内存泄漏问题而添加的成员变量;

public:

 Test()

 {

   cout<<"Test::Test()"<<endl;

   mp=newint(100); // 申请 4 个字节堆空间并初始化为 100;

   cout<<*mp<<endl;

 }

 ~Test()

 {

   deletemp; // 析构函数归还堆空间;但是如果仅仅用 free() 函数归还堆空间,这里析构函数没有调用,则对象没有摧毁,那么就造成了堆空间泄漏,这在大型项目开发中是不可原谅的;

   cout<<"~Test::Test()"<<endl;

 }

};

intmain()

{

 Test*pn=newTest; // 第一步申请堆空间,第二步(申请成功后)在堆空间上调用构造函数、因为需要初始化;

 Test*pm= (Test*)malloc(sizeof(Test)); // 这行代码运行完后,pm 并没有指向合法的对象,它仅仅指向一片内存空间而已,这个时候这片内存空间不能够成为一片合法的对象,因为就没有对象;

 deletepn; // 动态归还堆空间;第一步 delete 触发析构函数调用,摧毁对象,第二步归还堆空间;在归还堆空间的时候,要先摧毁掉对象,否则容易出现内存泄漏;

 free(pm); // 动态规划堆空间;仅归还堆空间,不触发析构函数调用;这里不能用 delete pm,因为这样会对非法对象调用构造函数,而对于析构函数中的 delete mp 来说,这样的影响是深远的,不知道什么时候就会带来 bug,且不可调试,只能通过“代码走查”的方式来检查是不是混用了两种类型的申请释放堆空间函数;

 return0;

}

1,结论:

   1,free() 可以释放由 new 申请来的堆空间,但是 free() 不会进行析构函数的调用,因此有可能造成内存泄漏;

   2,new 和 delete,malloc 和 free 只能匹配使用,不能混用;

3,new 和 malloc 的区别(自己、功能、应用):

 1,new 在所有 C++ 编译器中都被支持;

 2,malloc 在某些系统开发中是不能调用的;

 3,new 能够触发构造函数的调用;

 4,malloc 仅分配需要的内存空间;

 5,对象的创建只能使用 new;

 6,malloc 不适合面向对象开发;

4,下面的代码输出什么?为什么?

 1,代码示例:

intmain()

{

 Test*pn=newTest; // 调用构造函数;

 test*pm= (Test*)malloc(sizeof(Test)); // 仅申请堆空间;

 deletepn; // 调用析构函数;

 free(pm); // 仅释放堆空间;

  return0;

}

5,delete 和 free 的区别(自己、功能、应用):

 1,delete 在所有 C++ 编译器中都被支持;

 2,free 在某些系统开发中是不能调用;

 3,delete 能够触发析构函数的调用;

 4,free 仅归还之前分配的内存空间;

 5,对象的销毁只能使用 delete;

 6,free 不适合面向对象开发。

6,构造函数是否可以成为虚函数?析构函数是否可以成为虚函数?

7,构造函数不可能成为虚函数:

 1,在构造函数执行结束后,虚函数表指针才会被正确的初始化;

   1,C++ 里面的多态是通过虚函数表和指向虚函数表指针完成的,虚函数表指针是由编译器创建的,同时也是由编译器进行初始化,在构造函数执行结束之后,虚函数表的指针才会被正确进行初始化;

   2,在构造函数执行的过程当中,虚函数表的指针有可能是没有被正确初始化的,因为对于虚函数表和虚函数表指针的实现,对于不同的 C++ 编译器而言,实现有可能不一样,但是所有的 C++ 编译器都会保证在构造函数执行结束后,虚函数表指针肯定会被正确的初始化,在这之前,是没有保证的;

   3,所以构造函数不可能成为虚函数,创建一个对象的时候,我们需要构造函数来初始化虚函数表的指针,因此构造函数相当于一个入口点,这个入口点负责虚函数调用的前期工作,这个入口点当然不可能是虚函数;

8,析构函数可以成为虚函数:

 1,析构函数在对象销毁之前被调用,对象销毁之前意味着虚函数指针是正确的指向对应的虚函数表的;

 2,建议在设计类时将析构函数声明为虚函数(工程中设计一个父类的析构函数为虚函数);

   1,赋值兼容性申请子类对象给父类指针时,当 delete 作用在指针上时,编译器会直接根据指针类型(此时是父类)来调用相应的析构函数,若父类加上 virtual,编译器可以根据指针指向的实际对象(此时是子类)决定如何调用析构函数(多态);

9,构造、析构、虚函数编程实验:

#include <iostream>

#include <string>

using namespace std;

class Base

{

public:

 Base() // 若申请为析构函数,则编译器在此处显示:error:constructors cannot be declared virtual.

 {

   cout << "Base()" << endl;

 }

 virtual void func()

 {

   cout << "Base::func()" << endl;

 }

 virtual ~Base() // 申请为虚函数时,编译器无显示

 {  

   cout << "~Base()" << endl;

 }

};

class Derived : public Base

{

public:

 Derived()

 {

   cout << "Derived()" << endl;

 }

 virtual void func()

 {

   cout << "Derived::func()" << endl;

 }

 ~Derived()

 {

   cout << "~Derived()" << endl;

 }

};

int main()

{

 Base* p = new Derived();

 // ...

 delete p; // 期望调用完子类析构函数再调用父类的析构函数;但是如果父类没有申请为析构函数,则只调用父类析构函数;这是因为此时删除的是一个父类的指针,由于并没有将析构函数申请为 virtual,因此在这样情况下,编译器直接根据指针 p 的类型来决定调用哪一个构造函数,由于指针 p 的类型是父类的类型,所以编译器直接暴力认为调用父类构造函数就可以了;当将父类的虚函数声明为 virtual 时,编译器就不会简单的根据指针 p 的类型来简单调用父类的或者是子类的析构函数了,这个时候由于析构函数是虚函数,所以在执行这行代码的时候,编译器会根据指针 p 指向的实际对象来决定如何调用析构函数,这是多态;

 return 0;

}

1,工程中设计一个类作为父类出现时,我们都要将析构函数声明为虚函数,否 则就有可能产生内存泄漏,因为有可能跳过子类析构函数的调用,如果子类   析构函数中有释放资源的操作(动态内存空间),则后果不堪设想;

10,构造函数中是否可以发生多态?析构函数中是否可以发生多态?

11,构造函数中(构造函数中调用虚函数)不可能发生多态行为:

 1,在构造函数执行时,虚函数表指针未被正确初始化;

12,析构函数中(析构函数中调用虚函数)不可能发生多态行为:

 1,在析构函数执行时,虚函数表指针可能已经被摧毁;

13,析构函数和构造函数中(调用虚函数时)不能发生多态行为,只调用当前类中的函数版本;

 1,构造函数和析构函数中调用虚函数实验:

#include <iostream>

#include <string>

using namespace std;

class Base

{

public:

 Base()

 {

   cout << "Base()" << endl;

   func();

 }

 virtual void func()

 {

   cout << "Base::func()" << endl;

 }

 virtual ~Base()

 {

   func();

   cout << "~Base()" << endl;

 }

};

class Derived : public Base

{

public:

 Derived()

 {

   cout << "Derived()" << endl;

   func();

 }

 virtual void func()

 {

   cout << "Derived::func()" << endl;

 }

 ~Derived()

 {

   func();

   cout << "~Derived()" << endl;

 }

};

int main()

{

 Base* p = new Derived(); // 打印 Base(),Base::func(),Derived(),Derived::func(),                

 // ...

 delete p; // 打印 Derived::func(),~Derived(),Base::func(), ~Base();

 return 0;

}

14,继承中如何正确的使用强制类型转换?

 1,dynamic_cast 是与继承相关的类型转换关键字;

 2,dynamic_cast 要求相关的类中必须有虚函数;

 3,用于有直接或者间接继承关系的指针(引用)之间;

   1,指针:

     1,转换成功:得到目标类型的指针;

     2,转换失败:得到一个空指针;

   2,引用:

     1,转换成功:得到目标类型的引用;

     2,转换失败:得到一个异常操作信息;

 4,编译器会检查 dynamic_cast 的使用是否正确;

   1,在 C++ 编译器中得到足够重视,是非常有地位的一个类型转换关键字;

   2,使用不正确编译器会报错;

 5,类型转换的结果只可能在运行阶段才能得到;

   1,动态的类型转换,转换结果运行阶段才能得到;

15,dynamic_cast 的使用编程实验:

#include <iostream>

#include <string>

using namespace std;

class Base

{

public:

 Base()

 {

   cout << "Base::Base()" << endl;

 }

 virtual ~Base() // 工程经验;

 {

   cout << "Base::~Base()" << endl;

 }

};

class Derived : public Base

{

};

int main()

{

/*

   Base* p = new Derived;

 Derived* pd = p; // 编译器显示:error:invalid conversion from 'Base*' to 'Derived*';

          // 未有虚函数时,用 dynamic_cast 转换,编译器显示:error: cannot dynamic_cast 'p' (of type 'class Base*') to type 'Derived*' (source type is not polymorphic(多态的))

          // 有虚函数且用了 dynamic_cast 也要判断 pd 不为空;

*/

 Base* p = new Base;

 Derived* pd = dynamic_cast<Derived*>(p); // 不合法,不能使用子类指针,指向父类对象;编译器编译阶不报错;但是运行时 pd = 0; 意味着此处强制类型转换不成功;

 if( pd != NULL ) // 这样的判断很有必要;

 {

   cout << "pd = " << pd << endl;

 }

 else

 {

   cout << "Cast error!" << endl;

 }

 delete p;

 return 0;

}


目录
相关文章
|
1月前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
33 10
|
12天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
37 1
|
14天前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
36 3
|
21天前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
42 3
|
28天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
28天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
66 4
|
1月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
188 9
|
2月前
|
机器学习/深度学习 算法 物联网
大模型进阶微调篇(一):以定制化3B模型为例,各种微调方法对比-选LoRA还是PPO,所需显存内存资源为多少?
本文介绍了两种大模型微调方法——LoRA(低秩适应)和PPO(近端策略优化)。LoRA通过引入低秩矩阵微调部分权重,适合资源受限环境,具有资源节省和训练速度快的优势,适用于监督学习和简单交互场景。PPO基于策略优化,适合需要用户交互反馈的场景,能够适应复杂反馈并动态调整策略,适用于强化学习和复杂用户交互。文章还对比了两者的资源消耗和适用数据规模,帮助读者根据具体需求选择最合适的微调策略。
249 5
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。

热门文章

最新文章