• 《C++ Primer Plus》第九章:内存模型和名称空间(1)


    单独编译

    和C语言一样,C++也允许甚至鼓励程序员将组件函数放在独立的文件中。可以单独编译这些文件,然后将它们链接成可执行的程序。(通常,C++编译器即编译程序,也管理链接器。)如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。

    UNIX 和 linux 系统都具有 make 程序,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行 make 时,如果它检测到上次编译后修改了源文件,make 将记住重新构建程序所需的步骤。大多数集成开发环境都在project菜单中提供了类似的工具。

    头文件中常包含的内容:

    • 函数声明
    • 使用#define 或 const 定义的符号常量
    • 结构声明
    • 类声明
    • 模板声明
    • 内联函数

    将结构声明放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为const的数据和内联函数有特殊的链接属性,因此可以将其放在头文件中,而不会引起问题。

    注意,在包含头文件时,使用"headfile.h",而不是,如果文件名包含在尖括号中,则 C++ 编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其它目录,这取决于编译器)。如果没有在那里找到头文件,则将在标准位置查找。因此在包含自己的头文件时,应使用引号而不是尖括号。

    警告:在IDE中,不要将头文件加入到项目列表中,也不要在源代码文件中使用 #include 来包含其它源代码文件。

    头文件管理:

    #ifndef COORDIN_H_
    #define COORDIN_H_
    ..
    #endif
    
    • 1
    • 2
    • 3
    • 4

    由于C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰。在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误。

    存储持续性、作用域和链接性

    C++11使用4种不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。

    • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
    • 静态存储持续性:在函数定义外定义的变量和使用关键字 static 定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有三种存储持续性为静态的变量。
    • 线程存储持续性:当前,多核处理器很常见,这些 CPU 可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字 thread_local 声明的,则其生命周期与所属的线程一样长。
    • 动态存储持续性:用 new 运算符分配的内存将一直存在,直到使用 delete 运算符将其释放或程序结束为止。这种内存的存储持续性为动态,又是被称为自由存储(free store)或堆(heap)。

    作用域和链接

    作用域(scope)描述了名称在多大范围内可见。
    例如,函数中定义的变量可在该函数中使用,但不能在其它函数中使用;而在文件中的函数定义之前定义的变量可在所有函数中使用。

    链接性(linkage)描述了名称如何在不同单元间共享。
    链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。

    C++ 变量的作用域有多种。作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起的一系列语句。例如函数体就是代码块,但可以在函数体中嵌入其他代码块。作用域为全局(也叫文件作用域)的变量在定义位置到文件尾之间都可用。自动变量的作用域是局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数声明中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因)。在类中声明的成员的作用域为整个类。在名称空间中声明的变量的作用域为整个名称空间(由于名称空间已经引入到C++语言中,因此全局作用域是名称空间作用域的特例)。

    C++函数的作用域可以是整个类或整个名称空间(包括全局的),但不能是局部的(因为不能在代码块内定义函数,如果函数的作用域为局部,则只对它自己是可见的,因此不能被其他函数调用。这样的函数将无法运行)。

    不同的C++存储方式是通过存储持续性、作用域和链接性来描述的。

    自动存储持续性

    在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。当程序开始执行这些变量所属的代码块时,将为其分配内存;当函数结束时,这些变量都将消失。

    如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。
    如果内部代码块定义了和外部代码块同名的变量,那么在执行内部代码块时,该名称的变量将被解释为内部代码块定义的那个。可以形象地说,内部代码块的定义隐藏了以前的定义,在程序离开内部代码块时,原来的定义又重新可见。

    自动变量的初始化

    可以使用任何在声明时其值为已知的表达式来初始化自动变量。

    自动变量和栈

    程序使用两个指针来跟踪栈,一个指针指向栈底——栈的开始位置,另一个指针指向栈顶——下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。

    寄存器变量

    关键字 register 最初是由C语言引入的,它建议编译器使用 CPU 寄存器来存储自动变量:

    register int count_fast;		// request for a register variable
    
    • 1

    这旨在提高访问变量的速度。
    在 C++11 之前,这个关键字在 C++ 中的用法始终未变,只是随着硬件和编译器变得越来越复杂,这种提示表明变量用得很多,编译器可对其做特殊处理。在 C++11 中,这种提示作用也失去了,关键字 register 只是显式地指出变量是自动的。保留关键字 register 的重要原因是,避免使用了该关键字的现有代码非法。

    静态持续变量

    C++为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。

    这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。
    由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(比如栈)来管理它们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。

    另外,如果没有显式地初始化静态变量,编译器将把它们设置为0。在默认情况下,静态数组和结构将每个元素的所有位都设置为0。

    下面介绍如何创建这3种静态持续变量,然后介绍它们的特点。
    要创建链接性为外部的静态持续变量,必须在代码块的外面声明它;
    要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用关键字static;
    要创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static限定符。

    ...
    int gloabal = 1000;			// static duration, external linkage
    static int one_file = 50;	// static duration, internal linkage
    ...
    void func(int n){
    	static int count = 0;	// static duration, no linkage
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    静态变量的初始化

    除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。
    零初始化即将变量设置为零,对于标量类型,零将被强制转换为合适的类型。例如,在C++中,空指针用0表示,但内部可能采用非零表示,因此指针变量零初始化时将被设置为空指针相应的内部表示。结构成员被零初始化,且填充位都被设置为零。

    零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译文件时初始化变量。动态初始化意味着变量将在编译后初始化。

    首先,所有静态变量都被零初始化,然后,如果编译器仅根据文件内容就可计算表达式,编译器将执行静态初始化;但如果编译器没有足够的信息,变量将被动态初始化。
    比如:

    #include
    const double pi = 4.0 * atan(1.0);	// dynamic initialization
    
    • 1
    • 2

    要初始化pi,必须调用函数atan(),这需要等到atan()被链接且程序被执行时。

    但注意,使用sizeof运算符的表达式也可以是常量表达式,所以下面的语句执行的是静态初始化:

    int enough = 2 * sizeof(long) + 1;	// constant expression initialization
    
    • 1

    C++11新增了关键字 constexpr,这增加了创建常量表达式的方式。但本书不会更详细地介绍 C++11 新增的这项功能。

    静态持续性、外部链接性

    1. 单定义规则
      一方面,在每个使用外部变量的文件中,都必须声明它;另一方面,根据“单定义规则”,变量只能有以此定义。为满足这种需求,C++提供了两种变量声明,一种是定义声明(defining declaration),它给变量分配存储空间;另一种是引用声明,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字 extern,且不进行初始化;否则,声明为定义,导致分配存储空间:

      double up;		// definition, up is 0;
      extern int blem;	// blem defined elsewhere
      extern char gr = 'z';	// definition because initialized
      
      • 1
      • 2
      • 3

      如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字 extern 声明它:

      // file01.cpp
      extern int cats = 20;	// definition because of initialization
      int dogs = 22;			// also a definition
      int fleas;				// also a definition
      ...
      // file02.cpp
      extern cats;			// not definitions they use
      extern dogs;			// extern and have no initialization
      ...
      // file03.cpp
      extern int cats;
      extern int dogs;
      extern int fleas;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      file02.cpp没有重新声明fleas,因此无法访问它。在file01.cpp中,省略关键字 extern 效果也相同。
      另外,还可以在函数块内使用关键字extern来声明定义过的外部变量,如果不使用extern,则函数块内新定义的同名变量会隐藏外部变量。但相比于使用extern来声明外部变量然后使用,更好和更安全的选择是使用作用域解析运算符(:😃。

    2. 全局变量和局部变量
      全局变量很有吸引力——因为所有的函数都能访问全局变量,因此不用传递参数。但易于访问的代价很大——程序不可靠。计算经验表明,程序越能避免对数据进行不必要的访问,就越能保持数据的完整性。通常情况下,应使用局部变量,应在需要知晓时才传递数据,而不应不加区分地使用全局变量来使数据可用。OOP在数据隔离方面又向前迈进了一步。
      然而,全局变量也有它们的用处。例如,可以让多个函数使用同一个数据块。外部存储尤其适合常量数据,可以使用关键字 const 来防止数据被修改。

    静态持续性、内部链接性

    将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。
    如果要在其他文件中使用相同的名称来表示其他变量,只省略关键字extern是不可行的,因为它违反了单定义原则,还需要加上static以表示链接性为内部,只作用于所在文件。

    静态存储持续性、无链接性

    静态持续家族中的第三个成员——无链接性的局部变量。这种变量是这样创建的,将 static 限定符用于在代码块中定义的变量。在代码块中使用 static 时,将导致局部变量的存储持续性为静态的。这意味着虽然该变量只在该代码块中可用,但它在该代码块处于不活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用该函数时,将不会像自动变量那样再次被初始化。

    说明符和限定符

    • auto (在C++11中不再是说明自动类型变量的说明符,而是表示自动类型推断)
    • register(在C++11中不再是表示将变量用寄存器存储,而只是保留以兼容以前的代码)
    • static;
    • extern;
    • thread_local;
    • mutable;

    static 被用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的。
    extern 表明是引用声明,即声明引用在其他地方定义的变量。
    thread_local 指出变量的持续性与其所属线程的持续性相同。thread_local 变量之于线程,犹如常规静态变量之于整个程序。
    mutable的含义将根据const来解释

    1. c-v限定符
    • const;

    • volatile;

      cv表示const和volatile。最常用的cv-限定符是const。

      关键字volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。听起来似乎很神秘,实际上并非如此。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)可能修改其中的内容。或者两个程序可能互相影响,共享数据。
      该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为 volatile,则编译器将进行这种优化;将变量声明为 volatile,相当于告诉编译器,不要进行这种优化。

    1. mutable
      现在回到 mutable。可以用它来指出,即使结构(或类)变量为 const,其某个成员也可以被修改。例如,请看下面的代码:

      struct data{
      	char name[30];
      	mutable int accesses;
      	...
      }
      const data veep = {"Claybourne Clodde", 0, ... };
      strcpy(veep.name, "Joye Joux");		// not allowed
      veep.accesses++;	// allowed
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      veep的const限定符禁止程序修改 veep 的成员,但 access 成员的 mutable 说明符使得 access 不受这种限制。

      本书不使用 volatile 或 mutable,但将进一步介绍 const。

    2. 再谈 const
      在C++中,const限定符对默认存储类型稍有影响——在默认情况下全局变量的链接性为外部的,但 const 全局变量的链接性为内部的。也就是说,在 C++ 看来,全局 const 定义(如下述代码所示)就像使用了 static 说明符一样。

      const int fingers = 10;		// same as static const int fingers = 10;
      int main(){
      ...
      
      • 1
      • 2
      • 3

      假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件。那么,预处理器将头文件的内容包含到每个源文件中后,所有的源文件都将包含类似下面的定义:

      const int fingers = 10;		
      const char * warning = "Wak!";
      
      • 1
      • 2

      然而,由于外部定义的 const 数据的链接性为内部的,因此可以在所有文件中使用相同的声明。
      内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的。
      这就是能够将常量定义放在头文件中的原因。这样,只要在两个源代码文件中包括同一个头文件,则它们将获得值相同但地址不同的常量。
      出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用 extern 关键字来覆盖默认的内部链接性:

      extern const int states = 50;	// definition with external linkage
      
      • 1

      在这种情况下,必须在所有使用该常量的文件中使用 extern 关键字来声明它。但由于定义和声明都使用了extern关键字,需要特别注意只有一个文件可对其进行初始化。

      在函数或代码块中声明 const 时,其作用域为代码块,即仅当程序执行该代码中的代码时,该常量才是可用的。这意味着在函数或代码块中创建常量时,不必担心其名称与其他地方定义的常量发生冲突。

    函数和链接性

    和C语言一样,C++ 不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都为静态的,即在整个程序执行期间都一直存在。在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上可以在函数声明中使用关键字 extern 来指出函数是在另一个文件中定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)。还可以使用关键字 static 将函数的链接性设置为内部的,使之只能在一个文件中使用。必须在声明和函数定义中都使用该关键字:

    static int private(double x);
    ...
    static int private(double x){
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这意味着该函数只在这个文件中可见,还意味着可以在其他文件中定义同名的函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数。

    单定义原则也适用于非内联函数,因此对于每个非内联函数,程序只能包含一个定义。对于链接性为外部的函数来说,这意味着在多文件程序中,只能有一个文件(该文件可能是库文件,而不是程序员编写的)包含该函数的定义,但使用该函数的每个文件都应包含其函数声明。

    内联函数不受这项规则的约束,这允许程序员能够将内联函数的定义放在头文件中。这样,包含了头文件的每个文件都有内联函数的定义。然而,C++ 要求同一个函数的所有内联定义都必须相同。

    C++在哪里查找函数?
    假设在程序的某个文件中调用一个函数,C++ 将到哪里去寻找该函数的定义呢?如果该文件中的函数声明指出该函数是静态的,则编译器将只在该文件中查找函数定义;否则,编译器(包括链接程序)将在所有的程序文件中查找。如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义。如果在程序文件中没有找到,编译器将在库中搜索。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数(然而,C++保留了标准库函数的名称,即程序员不应使用它们)。有些编译器-链接程序要求显式地指出要搜索哪些库。

    语言链接性

    另一种形式的链接性——称为语言链接性(language linking)也对函数有影响。首先介绍一些背景知识。链接程序要求每个不同的函数都有不同的函数名。在C语言中,一个名称只对应一个函数,因此这很容易实现。为满足内部需要,C语言编译器可能将 spiff 这样的函数名翻译为 _spiff。这种方法被称为C语言链接性(C language linkage)。但在 C++ 中,同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此C++编译器执行名称修饰,为重载函数生成不同的函数名称。例如,可能将 spiff(int) 转换为 _spoff_i,而将spiff(double,double)转换为_spiff_d_d。这种方法被称为C++语言链接(C++ language linkage)。

    链接程序寻找与C++函数调用匹配的函数时,使用的方法与 C 语言不同。但如果要在 C++ 程序中使用 C 库中预编译的函数,将出现什么情况呢?例如,假设有下面的代码:

    spiff(22);	// want spiff(int) from a C library
    
    • 1

    它在 C 库文件中的符号名称为_spiff,但对于我们假设的链接程序来说,C++ 查询约定是查找符号名称_spiff_i。为解决这种问题,可以用函数声明来指出要使用的约定:

    extern "C" void spiff(int);		// use C protocol for name look-up
    extern void spoff(int);			// use C++ protocol for name look-up
    extern "C++" void spaff(int);	// use C++ protocol for name look-up
    
    • 1
    • 2
    • 3

    第一个声明使用C语言链接性;而后面的两个使用C++语言链接性。第二个声明是通过默认方式指出这一点的,而第三个显式地指出了这一点。

    C 和 C++ 链接性是 C++ 标准指定的说明符,但实现可提供其他语言链接性的说明符。

    存储方案和动态分配

    动态内存由运算符 new 和 delete 控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。与自动内存不同,动态内存不是LIFO,其分配和释放顺序要取决于 new 和 delete 在何时以何种方式被使用。通常,编译器使用三块独立内存:一块用于静态变量(可能再细分)、另一块用于动态存储。

    虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。例如,假设在一个函数中包含下面的语句:

    float * p_fees = new float [20];
    
    • 1

    由 new 分配的 80 个字节(假设 float 为 4 个字节)的内存将一直保留在内存中,直到使用delete运算符将其释放。但当包含该声明的语句块执行完毕时,p_fees 指针将消失。如果希望另一个函数能够使用这80个字节中的内容,则必须将其地址传递或返回给该函数。另一方面,如果在函数外声明 p_fees ,则文件中位于该声明后面的所有函数都可以使用它。另外,通过在另一个文件中使用下述声明,便可在其中使用该指针:

    extern float * p_fees;
    
    • 1

    注意:在程序结束时,由 new 分配的内存通常都将被释放,不过情况也并不总是这样。例如,在不那么健壮的操作系统中,在某些情况下,请求大型内存块将导致该代码块在程序结束不会被自动释放。最佳的做法是,使用 delete 来释放 new 分配的内存。

    1. 使用 new 运算符初始化
      如果要初始化动态分配的变量,该如何办呢?在 C++98中,有时候可以这样做,C++11 增加了其他可能性。
      如果要为内置的标准类型(如int或double)分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起:

      int *pi = new int (6);		// *pi set to 6
      double *pd = new double (99.99);	// *pd set to 99.99
      
      • 1
      • 2

      这种括号语法也可用于有合适构造函数的类,这将在本书后面介绍。

      然而,要初始化常规结构或数组,需要使用大括号的列表初始化,这要求编译器支持 C++ 11. C++ 11 允许这样做:

      struct where { double x; double y; double z;};
      where * one = new where { 2.5, 5.3, 7.2};	// C++11
      int * ar = new int [4] {2,4,6,7};			// C++11
      
      • 1
      • 2
      • 3

      在 C++11 中,还可将列表初始化用于单值变量:

      int *pin = new int {6};		// *pin set to 6
      double * pdo = new double {99.99};	// *pdo set to 99.99
      
      • 1
      • 2
    2. new 失败时
      new 可能找不到请求的内存量。在最初的 10 年,C++ 在这种情况下让 new 返回空指针,但现在将引发异常 std::bad_alloc

    3. new: 运算符、函数和替换函数
      运算符 new 和 new[] 分别调用如下函数:

      void * operator new(std::size_t);	// used by new
      void * operator new(std::size_t);	// used by new[]
      
      • 1
      • 2

      这两个函数被称为分配函数,它们位于全局名称空间中。同样,也有由 delete 和 delete[]调用的释放函数:

      void operator delete(void *);
      void operator delete[](void *);
      
      • 1
      • 2

      它们使用第11章将讨论的运算符重载语法。std::size_t 是一个 typedef,对应于合适的整型。对于下面这样的基本语句:

      int * pi = new int;
      
      • 1

      将被转换为下面这样:

      int *pi = new(sizeof(int));
      
      • 1

      而下面的语句:

      int *pa = new int[40];
      
      • 1

      将被转换成下面这样:

      int *pa = new(40*sizeof(int));
      
      • 1

      正如您知道的,使用运算符 new 的语句也可包含初始值,因此,使用 new 运算符时,可能不仅仅是调用 new() 函数。
      同样,下面的语句:

      delete pi;
      
      • 1

      将转换为如下函数调用:

      delete(pi);
      
      • 1

      有趣的是,C++ 将这些函数称为可替换的(replaceable)。这意味着如果您有足够的知识和意愿,可为 new 和 delete 提供替换函数,并根据需要对其进行定制。例如,可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配需求。在代码中,仍将使用 new 运算符,但它将调用您定义的 new() 函数。

    4. 定位 new 运算符

      通常,new 负责在堆(heap)中找到一个足以能够满足要求的内存块。new 运算符还有另一种变体,被称为定位new运算符,它让您能够指定要使用的位置。程序员可能使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。

      要使用定位new特性,首先需要包含头文件 new,它提供了这种版本的 new 运算符的声明;然后将 new 运算符用于提供了所需地址的参数。除需要指定参数外,句法与常规 new 运算符相同。具体地说,使用定位 new 运算符时,变量后面可以有方括号,也可以没有。下面的代码段演示了 new 运算符的4种用法:

      #include
      struct chaff{
      	char dross[20];
      	int slag;
      };
      char buffer1[50];
      char buffer2[500];
      int main(){
      	chaff *p1, *p2;
      	int *p3, *p4;
      	
      	p1 = new chaff;		// place structure in heap
      	p3 = new int[20];	// place int array in heap
      	p2 = new (buffer1) chaff;	// place structure in buffer1
      	p4 = new (buffer2) int[20];	// place in array in buffer2
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
  • 相关阅读:
    【插槽】Vue中插槽Slot基本使用和具名插槽
    CSS属性 - display
    Java 类型转换和运算符计算
    打开转盘锁 -- BFS
    【华为认证超全科普帖】
    Qt使用Json
    03-Nginx性能调优与零拷贝
    全连接神经网络结构图,神经网络示意图怎么画
    分布式前修课:MySQL实现分布式锁
    ros学习笔记13——unknown package [sensor_msgs] on search path [{{‘ros_to_deepstream
  • 原文地址:https://blog.csdn.net/weixin_40064300/article/details/128145750