一、demo
#include <stdio.h> #include <stdlib.h> #include <string.h> // C语言中所有的局部变量都在栈区 char * get_str_wrong(){ char s[10] = "hello"; // s 在栈区 return s; // 函数结束后,栈区的变量就销毁了 } char * get_str_right(){ char *s = malloc(10); // 在堆区申请内存,永远存在,除非手动释放 strcpy(s,"hello"); printf("%p ",s); return s; } int main(){ char *s = get_str_right(); printf("%p ",s); puts(s); free(s);// 释放内存 int a = 1; // 4 个字节 1024字节 = 1KB 1M = 1024 KB 512M 64M float b = 1.0; // 4 个字节 char c = '_'; // 1 个字节 printf("%d",sizeof(a)); printf("%d",sizeof(b)); printf("%d",sizeof(c)); }
C语言中,内存分配方法有2种:
- 第一种:int a = 1; char s[10] = "hello"; 在栈区申请内存
- 第二种:malloc()在堆区申请内存, free()释放内存
Python语言中:所有的对象都在堆区申请内存
- 释放内存,Python的内存管理器负责申请、释放内存
- 引用计数: 记录一个对象被引用的次数
- 垃圾回收: 当引用计数为0时,回收对象所占的内存
Python 没有C语言省内存
- Python 存int类型的数据: 引用的个数,类型,值
- C语言存int类型的数据:值
- Python用更多的内存换来了语言的易用性
分配内存:
底层源码与分配内存相关的函数有:
PyObject* _PyObject_New(PyTypeObject *type);
void* PyMem_RawRealloc(void *p, size_t n);
void* PyMem_RawCalloc(size_t nelem, size_t elsize);
TYPE* PyMem_New(TYPE, size_t n);
……
分配内存 全部都是在堆中分配的。
堆内存的特点(分配后,只能手动销毁) (PS: 栈内存的特点,出了作用域自动销毁)
在Python中,Python解释器负责销毁
引用计数:
sys.getrefcount(obj) 可以查看obj被引用了多少次
调用getrefcount函数时,引用+1
+1的情况:
- 赋值 、引用 x = obj obj = MyClass()
- 被其他对象使用 objects = [] objects.append(obj)
- 传给函数
-1的情况:
- 改变引用对象: x = obj x = 1
- 删除 del x
- 函数调用结束
cpython中有个结构体,里面有个字段专门用来存计数值
class MyClass(object): pass def f(obj): # print(sys.getrefcount(obj)) pass def refcount_test(): obj = MyClass() x = obj del x y = obj f(obj) print(sys.getrefcount(obj)) if __name__ == '__main__': refcount_test()
垃圾回收:
引用计数为0时,垃圾回收。python解释器负责
底层源码与垃圾回收相关的函数有:
void PyMem_Del(void *p);
void PyObject_Del(void *op);
void PyMem_RawFree(void *p);
堆内存分配在CPython 中的应用:
在 Python 中,内存管理涉及到一个包含所有 Python 对象和数据结构的私有堆(heap),这个私有堆的管理由内部的 Python 内存管理器(Python memorymanager) 负责。
在最底层,一个原始内存分配器通过与操作系统的内存管理器交互,确保私有堆中有足够的空间来存储所有与 Python 相关的数据。
内存分配器根据每种对象类型的特点实现不同的内存管理策略。
Python 堆内存的管理是由解释器来执行,用户对它没有控制权。
先看几个从cpython 源码中的函数:
void* PyMem_RawRealloc(void *p, size_t n); // 将 *p* 指向的内存块大小调整为 *n* 字节。 void* PyMem_RawCalloc(size_t nelem, size_t elsize); //分配 nelem 个元素,每个元素的大小为 elsize 字节 void* PyMem_RawMalloc(size_t n); // 分配 n 个字节,并返回一个指向分配的内存的 void* 类型指针 void PyMem_RawFree(void *p); // 释放 p 指向的内存块。 TYPE* PyMem_New(TYPE, size_t n); // 与 PyMem_Malloc() 相同,但分配 (n* sizeof(TYPE)) 字节的内存,返回一个转换为 TYPE* 的指针 TYPE* PyMem_Resize(void *p, TYPE, size_t n); // 与 PyMem_Realloc() 相同,但内存块的大小被调整为 (n * sizeof(TYPE)) 字节 void PyMem_Del(void *p); // 与 PyMem_Free() 相同
对象的创建和销毁:
TYPE* PyObject_New(TYPE, PyTypeObject *type); // 使用 C 结构类型 TYPE和 Python 类型对象 type 分配一个新的 Python 对象。 void PyObject_Del(void *op); //释放内存
PyObject* PyObject_Init(PyObject *op, PyTypeObject *type); //Return value: Borrowed reference. PyObject* _PyObject_New(PyTypeObject *type); // Return value: New reference. PyObject_Init 使用其类型和初始引用初始化新分配的对象操作。返回初始化的对象。new创建好的,然后进行初始化 PyObject_New 使用C结构类型类型和Python类型对象类型分配一个新的Python对象。原创
再看一个对象分配器:
pymalloc // 默认内存分配器,为小对象(小于或等于 512 字节), 它使用固定大小为256 KiB 的称为 "arenas" 的内存映射 PyMem_RawMalloc() 或 PyMem_RawRealloc() // 大于512字节的分配,刚开始创建用PyMem_RawMalloc(),动态扩容用PyMem_RawRealloc()
Python和C语言分配内存的关系:
pymalloc 分配器使用如下函数:
- Windows 上的 VirtualAlloc() and VirtualFree() ,
- mmap() 和 munmap() ,如果可用,
- 否则, malloc() 和 free() 。
VirtualAlloc() :Win32函数,预定指定地址和大小的虚拟内存空间。假如我们的程序要使用5 - MB内存块,但并不是要马上全部使用,则我们可以调用VirtualAlloc 函数
VirtualFree() :释放VirtualAlloc() 分配的内存
malloc() :C语言的内存分配函数,内部其实调用的是HeapAlloc 函数, HeapAlloc 函数是Windows提供的API,在进程初始化的时候,系统会在进程的地址空间中创建1 M大小的堆,称为默认堆(Default Heap)
free() :释放malloc() 分配的内存
mmap() 和 munmap() :Linux系统中的内存分配和释放函数。
在Python中,为什么int类型是28个字节?
import sys x = 8 # 值:8 类型:int 引用:1 print(type(1)) # <class 'int'> print(sys.getsizeof(1)) # 28
typedef struct { Py_ssize_t ob_refcnt; // 8 字节 引用计数值 struct _typeobject *ob_type; // 8字节 long ob_ival;// 8字节 } PyIntObject
思考:C语言 栈空间 和 堆空间 的区别?栈区 堆区 代码区 静态区