extern “C”的作用详解 +“free store” VS “heap”+修改线程默认栈空间大小+进程空间分配和堆栈大小

extern “C”的作用详解

  • 作用

    • 为能正确实现C++代码调用其他C语言代码。
  • 加extern "C"后,

    • 指示编译器这部分代码按C(而不是C++)方式编译
  • C++支持函数重载

    • 编译器编译函数的过程中会将函数的参数类型也加到编译后的代码
  • C不支持函

    • 编译C语言代码的函数时只包括函数名。
  • C++出现以前,很多代码都是C语言写的,

    • 很底层的库也是C写的
    • 为更好支持原来的C代码和已经写好的C语言库,
    • 需在C++中尽可能的支持C
    • extern "C"就是一个策略。
  • 用在下面情况

  • C++代码调用C语言代码

  • 在C++的头文件中用

  • 多人协同时

    • 有人擅长C,
    • 有人C++,
    • 这样的情况下也会用

例子:

  • moduleA、moduleB两模块
  • B调A中代码
  • A是用C实现
  • B用C++实现
//moduleA头文件
#ifndef __MODULE_A_H //宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA实现文件moduleA.C 
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB头文件
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, 
extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif//其他代码
 
#ifdef __cplusplus
}
#endif
#endif
//moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
  cout<<fun(2,3)<<endl;
}
  • void fun(int, int),
    • 编译后_fun_int_int(不同编译器可能不同,但都采用类似机制,用函数名和参数类型来命名编译后的函数名)
    • C没有重载机制
    • 一般是利用函数名来指明编译后的函数名的,
      • 上面的函数可能会是_fun

  • 为什么标准头文件都有类似的结构?

#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus             //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif
 
/*…*/
 
#ifdef __cplusplus
}
 
#endif
#endif /*end of __INCvxWorksh*/
  • extern "C"双含义

    • 首先,被它修饰的目标是"extern"的;
    • 其次,被它修饰的目标代码是"C"的。
  • 被extern "C"限定的函数或变量是extern类型

  • extern是C/C++中表明函数和全局变量的作用范围的关键字,

  • 该关键字告诉编译器,其申明的函数和变量可以在本模块或其他模块中使用

  • extern int a; 仅是一个变量的声明,其并不是在定义变量a,也并未为a分配空间

  • 变量a在所有模块中作为一种全局变量只能被定义一次,否则会出错

  • 在模块的头文件中对本模块提供给其他模块引用的函数和全局变量以关键字extern声明

  • 如果模块B要引用模块A中定义的全局变量和函数时只需包含模块A的头文件

  • 模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但不报错;

  • 链接阶段从模块A编译生成的目标代码中找到该函数。

  • extern对应的关键字是static,static表明变量或者函数只能在本模块中用,被static修饰的变量或者函数不可能被extern C修饰。

  • 被extern "C"修饰的变量和函数是按照C语言方式进行编译和链接的:很重要

  • 由于C++支持函数重载,而C语言不支持,因此函数被C++编译后在符号库中的名字是与C语言不同的;

  • C++编译后的函数需要加上参数的类型才能唯一标定重载后的函数,而加上extern "C"后,是为了向编译器指明这段代码按照C语言的方式进行编译

  • 未加extern "C"声明时的链接方式:

  • 未加extern "C"声明时的链接方式:
//模块A头文件 moduleA.h
#idndef _MODULE_A_H
#define _MODULE_A_H
 
int foo(int x, int y);
#endif
在模块B中调用该函数
//模块B实现文件 moduleB.cpp
#include"moduleA.h"
foo(2,3);

  • 链接阶段,链接器会从模块A生成的目标文件moduleA.obj中找_foo_int_int符号,不可能找到的,
  • foo()函数被编译成了_foo的符号,因此会出现链接错误。

extern "C"总结

  • 1,可以是如下的单一语句:
  • extern “C” double sqrt(double);

  • 2,相当于复合语句中的声明都加了extern “C”
extern "C"
{
      double sqrt(double);
      int min(int, int);
}

  • 3,可包含头文件,相当于头文件中的声明都加了extern “C”
extern "C"
{
    #include <cmath>
}
``` 



- 不可将extern "C" 添加在函数内部

- 如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
除extern "C", 还有extern "FORTRAN" 等。

canci

“free store” VS “heap”

  • C++内存布局
  • C++中,内存区分5
    • 堆、栈、自由存储区、全局/静态存储区、常量存储区”

  • 自由存储区与堆区别
  • “malloc在堆上分配的内存块,free释放内存,
  • new申请的内存则是在自由存储区,用delete来释放

  • 自由存储区与堆是两块不同的内存区域吗?
    • 可能相同吗?
  • 很多博客划分自由存储区与堆的分界线就是
    • new/delete与malloc/free。
  • C++标准没有要求
    • 但很多编译器的new/delete都以malloc/free为基础来实现
  • 借以malloc实现的new,所申请的内存是在堆上还是在自由存储区上?
  • 堆是操作系统维护的一块特殊内存,
    • 提供动态分配功能
    • 运行程序调malloc()时就从中分配,稍后调用free把内存交还
  • 自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区
  • 基本C++编译器默认用堆来实现自由存储
    • 即缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现
    • 这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确
    • 但也可通过重载操作符,改用其他内存来实现自由存储
    • 如全局变量做的对象池,这时自由存储区就区别于堆

  • 堆是操作系统维护的一块内存
  • 自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。
  • 堆与自由存储区并不等价。

问题的来源

  • 这个问题的起源在哪里。
  • 最先用C时,并没有这样的争议,
  • 很明确地知道malloc/free是在堆上进行内存操作。
  • 直到Bjarne Stroustrup的书籍中数次看到free store (自由存储区),说实话,我一直把自由存储区等价于堆。
  • Herb Sutter的《exceptional C++》中,指出free store(自由存储区) 与 heap有区别。
  • 自由存储区与堆是否等价的问题讨论,大概就是从这里开始的:
The free store is one of the two dynamic memory areas, allocated/freed by new/delete. 
Object lifetime can be less than the time the storage is allocated;
free store objects can have memory allocated without being immediately initialized, and can be destroyed without the memory being immediately deallocated. During the period when the storage is allocated but outside the object's lifetime, the storage may be accessed and manipulated through a void* but none of the proto-object's nonstatic members or member functions may be accessed, have their addresses taken, or be otherwise manipulated.


The heap is the other dynamic memory area, allocated/freed by malloc/free and their variants. 
while the default global new and delete might be implemented in terms of malloc and free by a particular compiler, the heap is not the same as free store and memory allocated in one area cannot be safely deallocated in the other. Memory allocated from the heap can be used for objects of class type by placement-new construction and explicit destruction. If so used, the notes about free store object lifetime apply similarly here
  • 作者指出,
  • 把堆与自由存储区要分开来,是因为在C++标准草案中关于这两种区域是否有联系的问题一直很谨慎地没有给予详细说明,而且特定情况下new和delete是按照malloc和free来实现,
  • 或者说是放过来malloc和free是按照new和delete来实现的也没有定论。
  • 这两种内存区域的运作方式不同、访问方式不同,所以应该被当成不一样的东西来使用。

结论

  • 自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。

  • new所申请的内存区域在C++中称为自由存储区。

    • 藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。
  • 堆与自由存储区还是有区别的,它们并非等价。

  • 你来自C,没接触过C++;

  • 或你一开始就熟悉C++的自由储存概念,

    • 从没听说过C的malloc,
    • 就不会陷入“自由存储区与堆好像一样,好像又不同”这样的迷惑之中。
    • 这就像Bjarne Stroustrup所说的:
      usually because they come from a different language background.

canci

修改线程默认栈空间大小

  • ulimit -s 查看linux的默认栈空间大小,
    • 默认 为10240 即10M
  • ulimit -s 设置大小值临时改变栈空间大小:
    • ulimit -s 102400, 即修改为100M
  • /etc/rc.local 内加入 ulimit -s 102400
    • 开机就设置栈空间大小
  • /etc/security/limits.conf 中也可以改变栈空间大小:
    #
  • soft stack 102400
    重新登录,执行ulimit -s 即可看到改为102400 即100M

Why does Linux have a default stack size soft limit of 8 MB?

  • protect the OS
  • Programs that have a legitimate reason to need more stack are rare.
  • sometimes said mistakes lead to code that gets stuck in an infinite loop.
  • if that infinite loop happens to contain a recursive function call,
    • the stack would quickly eat all the available memory.
  • soft limit on the stack size prevents this:
    • the program will crash but the rest of the OS will be unaffected.
  • this is only a soft limit,
    • you can actually modify it from within your program (see setrlimit(2): get/set resource limits) if you really need to.

进程空间分配和堆栈大小

1. Linux中进程空间

  • 进程的空间分配:
  • 与进程相关的数据结构(页表、内核栈、task)
  • 物理内存
  • 内核代码和数据
  • 用户栈
  • 共享库的内存映射
  • 运行时堆
  • 未初始化数据段.bss
  • 已初始化数据段.data
  • 代码段.text

2. 进程堆栈大小

  • 32位Windows,进程栈默认1M
    • vs的编译属性可修改程序运行时进程栈大小
  • Linux下进程栈默认10M
    • ulimit -s查看并修改默认栈大小
    • 默认一个线程要预留1M左右栈
    • 进程中有N个线程时,Windows下大概有N*M栈大小
  • 堆大小理论上大概=进程虚拟空间大小-内核虚拟内存大小。
  • windows
    • 进程高位2G留给内核,低2G给用户
      • 进程堆大小小于2G。
  • Linux下,进程高1G给内核,
    • 低3G给用户,
    • 所以进程堆大小小于3G。

3. 进程的最大线程数:

  • 32位windows,一个进程空间4G,内核占2G,留给用户只有2G,
  • 一个线程默认栈1M,
  • 所以一个进程最大开2048个线程。
  • 当然内存不会完全拿来做线程的栈,所以最大线程数实际值要小于2048,大概2000个。
  • 32位Linux下,用户留3G,
    • 一个线程默认8M,
    • 最多380个左右线程。
    • (ps:ulimit -a 查看电脑的最大进程数,大概7000多个)

canci

ultimate -s 查看一下是8192字节

  • 公司开发机上
  • 然后下面的代码执行就报错
#include <immintrin.h>
#include <iostream>
#include<stdio.h>
int main()
{
	long a = 0xffffffffffffffe0UL;
	int b[1024*1024*2];
	std::cout << a<< std::endl;
	for (int i = 0; i<8;i++)
	{
		std::cout << sizeof(0x1UL) << std::endl;
	}
	return 1;
}
  • 然后我执行 ultimate -s 16384
  • 然后我再执行上面的程序,这样就不出错了!

我在vivo手机上执行ulimit -s

shell@PD1603:/ $ ulimit -s                                                     
8192
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fgh431

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值