C++PrimerPlus 学习笔记 | 第九章 内存模型和命名空间|2.7 函数与链接性 & 2.8 语言与链接性 & 2.9 存储方案和动态分配

函数与链接性

与变量一样,函数也有链接性,虽然选择范围稍小,与C语言一样,C++不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都是静态的,即在程序执行期间一直存在,在默认情况下函数的链接性为外部,可以在所有文件中共享(与全局变量一样需要声明,实际上也可以与全局变量一样,用extern显式的指出该函数在其他文件处定义)。与全局变量一样的是也可以使用static修饰函数使得函数链接性变为内部的,使之只能在一个文件中使用。必须在原型和定义都使用static修饰。

使用static函数只在该文件中文件可见,还意味着在其他文件中可以定义同名函数,与全局变量一样,局部函数可以覆盖全局函数

即使外部定义了全局函数,如果该文件中存在static同名函数,程序仍将使用static修饰的函数。

单定义规则也同时适用于非内联函数,因此对于所有的非内联函数程序只能包含一个定义,对于链接性为外部的函数来说,这意味着在多文件程序中,只能有一个文件包含该函数的定义,但是所有使用该函数的文件都应该包含其函数原型

内联函数不适用于上述规则,这将允许程序员将内联函数放入头文件中,这样包含头文件的每个文件都有了内联函数定义,但是C++要求内联函数的定义全部相同.

C++在哪里查找函数

假设程序调用了一个函数,那么C++如何查找函数的定义呢?如果文件中函数的原型指出该函数是静态的,程序将只会在该文件中查找函数定义。否则程序将在所有文件中查找定义。如果找到两个定义,即报错,如果没有在文件中找到,C++将去库中搜索。这意味者如果程序定义了与库中同名的函数,C++将使用程序员定义的版本,

语言链接性

链接程序要求每个不同的函数都有不同的函数符号,在C语言中一个名称只对应一个函数,这很容易实现,为了满足内部需要,C语言一般将main函数翻译为_main,这种方法叫做C语言链接性,但是在C++中一个函数可能多个函数,必须将这些函数翻译为不同函数符号,为此C++将执行名称修饰,为重载函数生成不同的函数符号

// C
main() -> _main
print(double); -> _print;
// C++
main() -> _main;
print(int); -> _print_i;
print(double) -> _print_d;

C++这种方式叫做C++语言链接,链接程序寻找与C++函数调用匹配的函数时,方法与C语言不同,如果要在C++程序中使用C库预先编译好的函数,会有如下所示

spiff(22); // want spiff(int) from a C library
/*
他在C库文件中的符号名称为_spiff,对于我们假设的链接程序来说,C++查询符号名称是_spiff_i对于这种方法,我们可以用函数原型指出要使用的约定
*/
extern "C" void spiff(int); // C
extern "C++" void print(int); // C++
extern "C++" void print(double); // C++

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

这边很重要的一点是该库程序必须使用C编译器编译的库才需要指定查找约定,如果将C语言程序源码直接在C++编译器通过编译,他将默认使用C++查找约定,既不需要查找约定,简单来说一定是经过C语言编译器的库才需要语言查找约定

存储方案和动态分配

前面介绍的C++为内存分配的5种方案(线程内存除外)他不适用于C++运算符new(或者C函数malloc)分配的内存,这种内存称为动态内存。动态内存由new和delete控制,而不是由作用域和链接性规则控制,因此可以在一个函数中非配动态内存,而在另外一个函数中释放。编译器使用三种独立内存,一块用于静态变量,一块用于自动变量,另外一块用于动态存储。

虽然存储方案不适用不适用于动态内存,但是适用于跟踪动态内存的自动和静态的指针,如下

flaot * p_fees = new float(20);
// 由new分配的内存将一直保留在内存中,但是p_fees指针将在代码块结束后被释放,如果还有继续使用该内容,必须传递改地址或者返还给调用函数。或者将指针声明为全局变量,令所在该声明之后的函数都可以使用它,在另外一个文件使用下述声明也可以使用
extern flaot * p_fees;

一般来说由new分配的内存通常都会在程序结束时都被释放,不过情况并非一直如此,在不那么健壮的操作系统中,在某些情况的情况下请求的大型内存可能不会被自动释放,最佳的情况下使用delete来释放由new分配的内存

请不要丢失控制内存的指针,否则会导致内存泄露

https://zh.wikipedia.org/wiki/内存泄漏

使用new元素符初始化

如果要为内置的标量类型,如int或者double初始化并初始化,可在类型名加上初始值并用括号括起,实例如下

int * p_int = new int(-1);

这种括号语法也可以适用于由合适构造函数的类

在新的C++11中,可以使用大括号的列表初始化常规结构或数组,也可以初始化单值变量;实例如下

int * a = new int[10]{1,2,3,4,5,6,7,8,9,0}; // 使用大括号初始化数组
struct person {
	char name[1000];
	int age;
};
person * p = new person{"Llonvne",19}; // 使用大括号初始化结构
int * b = new int{2}; // C++也可以用大括号初始化单值变量与C++98括号类似
当new失败的时候

new可能找不到请求的内存量,在最初的十年中,C++让new返回空指针(与malloc相同),现在将引发异常:std::bad_alloc,

new,运算符,函数,替换函数

运算符new和new[]分配调用如下函数

void * operator new (std::size_t);
void * operator new[] (std::size_t);

这些函数被称为分配函数,位于全局名称空间中,同样也有释放函数如下

运算符delete和delete[]分配调用如下函数

void operator delete (void *);
void operator delete[] (void *);

它们使用第十一章使用的运算符重载的语法 如下所示

int * pi = new int;
int * pr = new(sizeof(int)); // ==

int * pa = new int[40];
int * pa = new(sizeof(int) * 40); // ==

delete也基本一致。

有趣的是C++讲这些函数称为可替换的,意味者你有足够的知识和意愿可为new和delete提供替换函数,并根据需要进行定制,例如可定于作用域为类的替换函数。在代码中仍然使用new运算符,但他将调用您定义的new()函数

定位new运算符

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

要使用定位运算符要包含头文件new,他包含了这种版本new运算符的原型,然后将new运算符用于提供了所需地址的参数,除了需要特殊的地址参数,其余参数类似,具体来说来说使用new运算符可以有方括号也可以没有。

实例

char buffer[512];

int * p = new (buffer) int[50]; // 将buffer所在的内存地址分配个int
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值