C中malloc分配内存中有没有malloc 字节对齐齐一说

Nginx学习笔记(五) 源码分析&内存模块&字节对齐 - cococo点点 - 推酷
Nginx学习笔记(五) 源码分析&内存模块&字节对齐 - cococo点点
Nginx源码分析&内存模块
今天总结了下C语言的内存分配问题,那么就看看Nginx的内存分配相关模型的具体实现。还有内存对齐的内容~~不懂的可以看看~~
src/os/unix/Ngx_alloc.h&Ngx_alloc.c
先上源码:
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
#ifndef _NGX_ALLOC_H_INCLUDED_
#define _NGX_ALLOC_H_INCLUDED_
#include &ngx_config.h&
#include &ngx_core.h&
void *ngx_alloc(size_t size, ngx_log_t *log);
void *ngx_calloc(size_t size, ngx_log_t *log);
#define ngx_free
* Linux has memalign() or posix_memalign()
* Solaris has memalign()
* FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
* aligns allocations bigger than page size at the page boundary
#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
#define ngx_memalign(alignment, size, log)
ngx_alloc(size, log)
extern ngx_uint_t
extern ngx_uint_t
ngx_pagesize_
extern ngx_uint_t
ngx_cacheline_
#endif /* _NGX_ALLOC_H_INCLUDED_ */
这里部分代码是关于内存的申请的,是对Linux原有的内存申请函数的再一次封装。
1.函数声明
void *ngx_alloc(size_t size, ngx_log_t *log);
//申请空间
void *ngx_calloc(size_t size, ngx_log_t *log);
//申请空间,并初始化为0
2.源码解析
void * ngx_alloc(size_t size, ngx_log_t *log)
p = malloc(size);//malloc就是返回一个void*指针,指向分配的size大小的内存
if (p == NULL) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
&malloc(%uz) failed&, size);
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, &malloc: %p:%uz&, p, size);
void * ngx_calloc(size_t size, ngx_log_t *log)
p = ngx_alloc(size, log);//调用上面的函数
ngx_memzero(p, size);//并初始化为0,#define ngx_memzero(buf, n) (void) memset(buf, 0, n)  }
3.POSIX_MEMALIGN与MEMALIGN申请对齐内存
,可以参考Linux man page:
#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
#define ngx_memalign(alignment, size, log)
ngx_alloc(size, log)
#if (NGX_HAVE_POSIX_MEMALIGN)
void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
err = posix_memalign(&p, alignment, size);//stdlib.h 新接口
if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
&posix_memalign(%uz, %uz) failed&, alignment, size);
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
&posix_memalign: %p:%uz @%uz&, p, size, alignment);
#elif (NGX_HAVE_MEMALIGN)
void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
p = memalign(alignment, size);//malloc.h 老接口
if (p == NULL) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
&memalign(%uz, %uz) failed&, alignment, size);
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
&memalign: %p:%uz @%uz&, p, size, alignment);
对齐跟数据在内存中的位置有关,
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”。 比如4字节的int型,其
应该位于4字节的边界上,即起始地址能够被4整除。
字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
具体方法:
指定对齐值:#pragma pack (value)时的指定对齐值value。
取消对齐值:#pragma pach ()
具体分析:
#pragma pack(1)
#pragma pack()
#pragma pach(2)
#pragma pack()
代码如上,想一想答案都是多少?
& sizeof(struct A)=10 &
//默认情况下,1字节的a在0x,而整形b只能放在0x(必须从4的整数倍开始)~0x,最后的c在0xx
sizeof(struct B)=8 & &
//分析同上
sizeof(struct C)=7 & &
//这里指定了对齐值为1,那么a在0x,b在0xx0000004,c在0xx
sizeof(struct D)=8 & &
//分析同上
A、B、C、D的内存地址如图:
&src/core/Ngx_palloc.h&内存池分析
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
#ifndef _NGX_PALLOC_H_INCLUDED_
#define _NGX_PALLOC_H_INCLUDED_
#include &ngx_config.h&
#include &ngx_core.h&
* NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
* On Windows NT it decreases a number of locked pages in a kernel.
#define NGX_MAX_ALLOC_FROM_POOL
(ngx_pagesize - 1)
#define NGX_DEFAULT_POOL_SIZE
(16 * 1024)
#define NGX_POOL_ALIGNMENT
#define NGX_MIN_POOL_SIZE
ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),
NGX_POOL_ALIGNMENT)
typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s
ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_
ngx_pool_cleanup_t
typedef struct ngx_pool_large_s
ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t
typedef struct {
ngx_pool_t
} ngx_pool_data_t;
struct ngx_pool_s {
ngx_pool_data_
ngx_pool_t
ngx_chain_t
ngx_pool_large_t
ngx_pool_cleanup_t
typedef struct {
} ngx_pool_cleanup_file_t;
void *ngx_alloc(size_t size, ngx_log_t *log);
void *ngx_calloc(size_t size, ngx_log_t *log);
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
void ngx_destroy_pool(ngx_pool_t *pool);
void ngx_reset_pool(ngx_pool_t *pool);
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);
#endif /* _NGX_PALLOC_H_INCLUDED_ */
#define NGX_DEFAULT_POOL_SIZE & &(16 * 1024),表示NGX默认的
内存池的大小为16*1024
结构体ngx_pool_data_t
内存数据块
,ngx_pool_s
内存池头部结构
typedef struct {
//当前内存池分配到此处,即下一次分配从此处开始
//内存池结束位置
ngx_pool_t
//内存池里面有很多块内存,这些内存块就是通过该指针连成链表的
ngx_uint_ //内存池分配失败次数
} ngx_pool_data_t;
//内存池的数据块位置信息
struct ngx_pool_s{
//内存池头部结构
ngx_pool_data_
//内存池的数据块
//内存池数据块的最大值
ngx_pool_t
* //指向当前内存池
ngx_chain_t
//该指针挂接一个ngx_chain_t结构
ngx_pool_large_t
//大块内存链表,即分配空间超过max的内存
ngx_pool_cleanup_t * //释放内存池的callback
//日志信息
3.创建和销毁内存池:
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log)//创建内存池{
ngx_pool_t
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
//申请对齐内存空间
if (p == NULL) {
return NULL;
p-&d.last = (u_char *) p + sizeof(ngx_pool_t);
//下一次分配的开始地址,sizeof(ngx_pool_t)为申请的P的大小
p-&d.end = (u_char *) p +             //内存池结束位置,size是申请空间的小小
p-&d.next = NULL;
//内存链表的指向下一内存块的指针为空
p-&d.failed = 0;
//失败次数
size = size - sizeof(ngx_pool_t);
p-&max = (size & NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
                                 //内存池最大块           
p-&current =
//当前指向的内存块
p-&chain = NULL;
p-&large = NULL;
p-&cleanup = NULL;
//该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构),亦将遍历该cleanup链表结构依次调用clenup的handler清理。同时,还将遍历large链表,释放大块内存。void ngx_destroy_pool(ngx_pool_t *pool)//删除全部内存池(链上的所有内存块)
ngx_pool_t
ngx_pool_large_t
ngx_pool_cleanup_t
  //根据注册的ngx_pool_cleanup_s 来逐个销毁内存
for (c = pool-& c = c-&next) {
   if (c-&handler) {
      ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool-&log, 0, &run cleanup: %p&, c);
      c-&handler(c-&data);
     }
//销毁大内存块
for (l = pool-& l = l-&next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool-&log, 0, &free: %p&, l-&alloc);
if (l-&alloc) {
ngx_free(l-&alloc);
#if (NGX_DEBUG)
* we could allocate the pool-&log from this pool
* so we cannot use this log while free()ing the pool
for (p = pool, n = pool-&d. /* void */; p = n, n = n-&d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool-&log, 0,
&free: %p, unused: %uz&, p, p-&d.end - p-&d.last);
if (n == NULL) {
  //普通内存池  for (p = pool, n = pool-&d. /* void */; p = n, n = n-&d.next) {
ngx_free(p);
if (n == NULL) {
4.重置内存池:
//该函数将释放所有large内存,并且将d-&last指针重新指向ngx_pool_t结构之后数据区的开始位置,同刚创建后的位置相同。void ngx_reset_pool(ngx_pool_t *pool)
ngx_pool_t
ngx_pool_large_t
//删除大内存块
for (l = pool-& l = l-&next) {
if (l-&alloc) {
ngx_free(l-&alloc);//专门用于释放大内存ngx_free()
//大内存块置为空
pool-&large = NULL;
//重新修改每个内存块的大小
for (p = p = p-&d.next) {
p-&d.last = (u_char *) p + sizeof(ngx_pool_t);
5.注册cleanup
//cleanup结构体
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_
ngx_pool_cleanup_t
//注册cleanup函数,为以后清除做准备
ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
ngx_pool_cleanup_t
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));//申请内存池
if (c == NULL) {
return NULL;
if (size) {
c-&data = ngx_palloc(p, size); //申请数据空间
if (c-&data == NULL) {
return NULL;
c-&data = NULL;
c-&handler = NULL;
c-&next = p-&
p-&cleanup =
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p-&log, 0, &add cleanup: %p&, c);
6.内存分配函数
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
主要介绍一下ngx_palloc()这个函数:
void * ngx_palloc(ngx_pool_t *pool, size_t size)
ngx_pool_t
if (size &= pool-&max) {//max与待分配内存进行比较
p = pool-&//从当前位置开始遍历pool链表
m = ngx_align_ptr(p-&d.last, NGX_ALIGNMENT);
if ((size_t) (p-&d.end - m) &= size) {
p-&d.last = m +
//成功分配size大小的内存
} while (p);
return ngx_palloc_block(pool, size);
//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存
return ngx_palloc_large(pool, size);
//大于max值,则在large链表里分配内存
其中的ngx_palloc_block()函数:
//该函数分配一块内存,并加入到内存池中static void * ngx_palloc_block(ngx_pool_t *pool, size_t size)
ngx_pool_t
*p, *new, *
psize = (size_t) (pool-&d.end - (u_char *) pool); //计算内存池大小
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool-&log); //申请与原来相同的大小,这样的话内存池就是以2的指数幂增大
if (m == NULL) {
return NULL;
new = (ngx_pool_t *)
//新的内存块
new-&d.end = m +
new-&d.next = NULL;
new-&d.failed = 0;
m += sizeof(ngx_pool_data_t);//让m指向该块内存ngx_pool_data_t结构体之后数据区起始位
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new-&d.last = m + //在数据区分配size大小的内存并设置last指针
current = pool-&
for (p = p-&d. p = p-&d.next) {
if (p-&d.failed++ & 4) { //失败4次以上移动current指针
current = p-&d.
p-&d.next = new; //将这次分配的内存块new加入该内存池
pool-&current = current ? current : new;
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致C中 malloc()分配堆内存实际的大小 - 博客频道 - CSDN.NET
《西决》 —— 克里斯·保罗
分类:-[小西南]-
以前相关笔记:、。
C动态分配的实际大小
Figure1:内存中的堆内存空间
假设从《The &C &Programming &Language》中推测正确,从未经动态分配的堆内存呈现上图形式。不连续的堆内存以“链”的形式联系:Heap1 -& Heap2 -&Heap3 -&Heap4-&Heap1。笔记将构成“堆链”的每个堆内存(如Heap1)称为“堆块”。malloc()/free()将每个堆块看作由两部分构成:“Header”和“可用堆内存”。在Header中包含了“指向下一个堆内存块的指针”、“本堆块的大小”。这样malloc()/free()就能更好地管理堆。
2 堆内存分配
[1] mallco()分配机制
根据C中malloc(n)函数动态分配堆的机制:分配堆内存的时候就依序由低到高的地址搜索“堆链”中的堆块,搜索到“可用堆内存”满足n的堆块(如Heap1)为止。若Heap1的“可用堆内存”刚好满足n,则将Heap1从“堆链”中删除,同时重新组织各堆块形成新的“堆链”;若Heap1的“可用堆内存”大小大于n,则将malloc(n)申请到的“Header” + &可用堆内存&部分从Heap1中分裂,将剩余的Heap1堆内存块重新加入“堆链”中。经分裂后的堆内存也包含“Header”和“可用堆内存”两部分(如图Figure
2),然后将由malloc()分配得到的“可用堆内存”返回给用户。若某块堆内存空间比较大(如Heap1),能够满足较小内存的多次申请,那么由malloc(n)多次申请的堆内存块都是连续被分配给用户的(因为有Header,所以用户使用的堆地址不连续)。
为方便free()释放堆空间,经malloc(n)分配给用户的堆空间也隐含一个Header。如下图所示:
Figure2:malloc()分配的堆内存结构
由于Header的构成的内存对齐,C中malloc(n)函数分配的堆内存会大于等于Header + n。
[2] 程序验证
为了测试C语言malloc()函数所分配堆的实际大小(Header 大小和内存对齐),编写了如下宏:
#define malloc_no_free(type, type_str)
printf(&%s\tsizeof(%s)=%d\t&, type_str, type_str, sizeof(type));\
type *pthis = NULL, *plast = NULL;
for(i = 0; i & 6; ++i){
if( NULL != (pthis = (type *)malloc(sizeof(type))) ){
printf(&%d\t&, (int)pthis - (int)plast);\
plast =/*a litte logic to p*/
/*free(pthis);*/
pthis = NULL;
printf(&\n\n&);
用宏写这一段函数式为了在C中能够传递任何数据类型的变量。如果传递给malloc()函数的参数较小,那么malloc()函数是极有可能在图中的Heap1对区域进行多次分配的,如此这段代码就能够测试出malloc()函数分配堆内存的实际大小。
将此宏在主函数中调用:
struct _sc{ };
struct _sic{ };
struct _sip{ struct _sip *p; };
struct _sdc{};
struct _s17{ char a[17]; };
int main()
malloc_no_free(char, &char&);
malloc_no_free(int, &int&);
malloc_no_free(double, &double&);
malloc_no_free(struct _sc, &_sc&);
malloc_no_free(struct _sic, &_sic&);
malloc_no_free(struct _sip, &_sip&);
malloc_no_free(struct _sdc, &_sdc&);
malloc_no_free(struct _s17, &_17&);
在codeblocks + GNU GCC compiler下编译通过并运行得到一下结果:
Figure3:程序运行结果
分析运行结果:
(1) 如果将宏中” free(pthis);”的注释去掉,则所有的地址差都为0。这恰好验证了free()堆地址的机制:假设将要被释放的堆内存的地址为p,free(p)将以p为首地址的堆内存块释放到堆链中的某个地址上,且此地址跟p值最邻近,这样就可以保证尽可能的使堆内存以大块的形式存在而不至于让堆内存成为碎片。
(2) 将” free(pthis);”注释
各结构体所占内存大小再一次体现了。在一大块堆之上用malloc()函数分配内存,真实得到的内存要比实际大。得到的真实内存块的大小为8(实为内存对齐大小值)的整数倍。当数据结构所占内存为16的倍数时,malloc()分配的内存会多出8个字节,这8个字节就是header。(将struct _s17内数组维数改为16倍数时可验证)。
从这里可以看出:Header所占堆内存大小为8字节,堆内存对齐也为8(会看内存对齐就不难观察出来)。
3 动态分配内存对齐
造就实际分配内存大于用户所需原因:
每次malloc(n)分配的堆内存由Header + n构成。Header造就的内存对齐。
[1] Header
在的第八章”The& UNIX System Interface”的第七节”Example – A Strorage Allocator”中用以下结构体描述了Header:
typedef long A
union header {
union header *
union &header用其成员s.ptr作为指向堆链表中下一个堆块,s.size保存此堆的大小。x本意是用于内存对齐。malloc()/free()就是通过以堆内存为存储空间隐藏于用户的Header结构体来合理地管理堆内存的。
[2] 内存对齐
《C圣经》中想用Align(long)作为内存对齐的值,但由于win32上sizeof(long) = 4,由于sizeof(union header) = sizeof(s) = 8,故而在malloc()返回的堆内存中存储数据的内存对齐数值为8。
所以,以上为每种数据类型所分配堆空间实际大小就得到了合理的解释(排除第一个为32 bytes):
Figure4:C中实际的动态分配
所以在适当的明白malloc()的实际分配之后,在以后程序中的动态分配过程中就要利用这个过程避免堆内存的浪费:许多单独的堆内存块可以合并在一起分配,这样程序在运行过程中使用的堆内存会减少,有利于程序压缩空间。
4 malloc()分配总结
对于C中的malloc(n)分配,有以下进一步的结论:
实际分配的堆内存是Header + n结构。返回给用户的是n部分的首地址。由于内存对齐值8,实际分配的堆内存大于等于sizeof(Header) + n。
所以在编程过程中,需要用内存对齐的知识合理的让malloc()分配的内存变得小一些。
C Note Over。
排名:第558名
(3)(425)小结sizeof(字节对齐、空类的sizeof,有一个虚函数的类的sizeof、strlen) - 编程当前位置:& &&&小结sizeof(字节对齐、空类的sizeof,有一个虚函数小结sizeof(字节对齐、空类的sizeof,有一个虚函数的类的sizeof、strlen)&&网友分享于:&&浏览:1次总结sizeof(字节对齐、空类的sizeof,有一个虚函数的类的sizeof、strlen)原地址:/index.php/wv/789点击打开链接
http://blog.csdn.net/freefalcon/article/details/54839
/chengxin1982/archive//1374575.html
http://blog.csdn.net/shimazhuge/article/details/8238038
结合了几篇博文,一起总结了sizeof的问题。
关于sizeof的问题,并且本人对这个问题也一直没有得到很好的解决,索性今天对它来个较为详细的总结,同时结合strlen进行比较,如果能对大家有点点帮助,这是我最大的欣慰了。
一、好首先看看sizeof和strlen在MSDN上的定义:
首先看一MSDN上如何对sizeof进行定义的:
01.sizeof&Operator
03.sizeof&expression
05.The&sizeof&keyword
gives the amount of storage, in bytes, associated with a variable or a type
06.(including
aggregate types). This keyword returns a value of type&size_t.
expression is either an identifier or a type-cast expression (a type specifier enclosed in
09.parentheses).
applied to a structure type or variable,&sizeof&returns
the actual size, which may include
12.padding
bytes inserted&for&alignment.
When applied to a statically dimensioned array,&sizeof
13.returns
the size of the entire array. The&sizeof&operator
cannot&return&the
size of dynamically
14.allocated
arrays or external arrays.
然后再看一下对strlen是如何定义的:
the length of a string.
05.Routine
Required Header:
06.strlen&&string.h&
08.size_t&strlen(&const&char&*string
09.Parameter
10.string:Null-terminated
11.Libraries
versions of the C run-time&libraries.
of these functions returns the number of characters in string, excluding the terminal
No&return&value
is reserved to indicate an error.
18.Remarks
of these functions returns the number of characters in string, not including the
20.terminating
null character. wcslen is a wide-character version of&strlen;
the argument of
is a wide-character string. wcslen and&strlen&behave
identically otherwise.
二、各类型的sizeof。
第一个例子:&基本数据类型的sizeof
这里的基本数据类型指short、int、long、float、double这样的简单内置数据类型,由于它们都是和系统相关的,所以在不同的系统下取值可能不同,这务必引起我们的注意,尽量不要在这方面给自己程序的移植造成麻烦。
一般的,在32位编译环境中,
sizeof WORD; //2
sizeof DWORD; //4
一般的,在64位编译环境中,int为8byte ,指针为8byte 。
第二个例子:&指针变量的sizeof
学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。
char* pc = &abc&;
char** ppc = &&pre name=&code& class=&java&&char* pc = &abc&;
char** ppc = &
void (*pf)(); // 函数指针
sizeof( pc ); // 结果为4
sizeof( pi ); // 结果为4
sizeof( ps ); // 结果为4
sizeof( ppc ); // 结果为4
sizeof( pf ); // 结果为4
指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。
第三个例子:&数组的sizeof
数组的sizeof值等于数组所占用的内存字节数,c风格字符串末尾还存在一个NULL(\0)终止符
02.sizeof(ss)
结果 4 ===》ss是指向字符串常量的字符指针
03.sizeof(*ss)
结果 1 ===》*ss是第一个字符
05.char&ss[]
06.sizeof(ss)
结果 11 ===》ss是数组,计算到\0位置,因此是10+1
07.sizeof(*ss)
结果 1 ===》*ss是第一个字符
09.char&ss[100]
10.sizeof(ss)
结果是100 ===》ss表示在内存中的大小 100×1
11.strlen(ss)
结果是10 ===》strlen是个函数内部实现是用一个循环计算到\0为止之前
13.int&ss[100]
14.sizeof(ss)
结果 400 ===》ss表示再内存中的大小 100×4
15.strlen(ss)
错误 ===》strlen的参数只能是char*
且必须是以''\0''结尾的
17.char&q[]=&abc&;
18.char&p[]=&a\n&;
19.sizeof(q),sizeof(p),strlen(q),strlen(p);
一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的,那么应该怎么求数组元素的个数呢?Easy,通常有下面两种写法:
char a1[] = &abc&;
int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度void foo3(char a3[3])
int c3 = sizeof( a3 ); // c3 == 4
void foo4(char a4[])
int c4 = sizeof( a4 ); // c4 == 4
//也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不再是数组类型,而是蜕变成指针,相当于char* a3,为什么?仔细想想就不难明白,我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗?不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。
char&szPath[MAX_PATH]如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)
第四个例子:结构体的sizeof
1为什么需要字节对齐?
计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
2字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
cout&&sizeof(s1)&& // 24
cout&&sizeof(s2)&& // 16
//同样是两个char类型,一个int类型,一个double类型,但是因为对齐问题,导致他们的大小不同。计算结构体大小可以采用元素摆放法,我举例子说明一下:首先,CPU判断结构体的对界,根据上面的结论,s1和s2的对界都取最大的元素类型,也就是double类型的对界8。然后开始摆放每个元素(满足准则1)。
//对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址是8了,所以d被放在了8(满足准则2),此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被保留,s1的大小变成了24(满足准则3)。
//对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,结构体在15处结束,占用总空间为16,正好是8的倍数。
通过上面的叙述,我们可以得到一个公式:
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )
3对于上面的准则,有几点需要说明:
1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
#define&offsetof(s,m)&(size_t)&(((s&*)0)-&m)
例如,想要获得S2中c的偏移量,方法为
size_t&pos&=&offsetof(S2,&c);&//&pos等于4
2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
看下面的例子:
char a[8];
cout&&sizeof(s1)&& // 8
cout&&sizeof(s2)&& // 8
cout&&sizeof(s3)&& // 9
cout&&sizeof(s4)&& // 16;
& s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。
&&所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。
3)&有一个影响sizeof的重要参量还未被提及,那便是编译器的pack指令。它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma
pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,公式如下:offsetof( item )
= min( n, sizeof( item ) )
#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2) // 必须在结构体定义之前使用
#pragma pack(pop) // 恢复先前的pack设置
&计算sizeof(S1)时,min(2, sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。
同样,对于sizeof(S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。
4)“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢?于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:
struct S5 { };
sizeof( S5 ); // 结果为1
第五个例子:含位域结构体的sizeof
位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。
C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
struct BF1
char f1 : 3;
char f2 : 4;
char f3 : 5;
其内存布局为:
| f1& |& f2&& | |& f3&&&& |&&&& |
---------------------------------
| &| | | &| | | &| | | | | | & &| | | |
---------------------------------
0&&&& 3 & & 7 8&&&&&&&& 13&&& 16 (byte)
位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。
struct BF2
char f1 : 3;
short f2 : 4;
char f3 : 5;
};| f1& |& f2&& | |& f3&&&& |&&&& |
---------------------------------
| &| | | &| | | &| | | | | | & &| | | |
---------------------------------
0&&&& 3 & & 7 8&&&&&&&& 13&&& 16 (byte) 压缩
f1 & & & & & & & & & & & & & & & & &f2
& & & & & &&
不压缩时,首先把f1放到1的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素f2是short类型,要放到2的对界上,离1最接近的地址是2了,所以f2被放在了2(满足准则2),此时下一个空闲地址变成了4,下一个元素&f3的对界是1,4可以满足,所以&f3放在了4,此时下一个空闲地址变成了5,结构体在地址5处结束。由于BF2的大小需要是2的倍数,所以5的空间被保留,BF2的大小变成了6(满足准则3)。
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
struct BF3
char f1 : 3;
char f3 : 5;
非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。
补充:不要让double干扰你的位域&
  在结构体和类中,可以使用位域来规定某个成员所能占用的空间,所以使用位域能在一定程度上节省结构体占用的空间。不过考虑下面的代码:&
 int i: 8;
 int j: 4;
 int a:3;
 int i: 8;
 int j: 4;
 int a:3;
cout&&sizeof(s1)&& // 24
cout&&sizeof(s2)&& // 24
cout&&sizeof(s3)&& // 24
cout&&sizeof(s4)&& // 16
  可以看到,有double存在会干涉到位域(sizeof的算法参考上一节),所以使用位域的的时候,最好把float类型和double类型放在程序的开始或者最后。
第六个例子:类的sizeof(类似结构体)
01.class&X
05.char&k;
08.cout&&sizeof(X)&&
结果 12 ===》内存补齐
09.cout&&sizeof(x)&&
结果 12 同上
//空类的sizeof,有一个虚函数的类的sizeof
class A{};
virtual ~C() {}
int _tmain(int argc, _TCHAR* argv[])
printf(&%d, %d, %d\n&, sizeof(A), sizeof(B), sizeof(C));//结果是 1 1 4
//class A是一个空类型,它的实例不包含任何信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio 2008中每个空类型的实例占用一个byte的空间。
//class B在class A的基础上添加了构造函数和析构函数。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。所以sizeof(B)和sizeof(A)一样,在Visual Studio 2008中都是1
// class C在class B的基础上把析构函数标注为虚拟函数。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间,因此sizeof(C)是4。
#include &stdafx.h&
#include &iostream&
//编译器为每个有虚函数的类都建立一个虚函数表(其大小不计算在类中),并为这个类安插一个指向虚函数表的指针,即每个有虚函数的类其大小至少为一个指针的大小4
void Function();
virtual void Function();
class C : public B
class D : public B
virtual void Function2();
static void Function();
class staticE
static int intV
static void fun(){}
void test1()
cout&&&sizeof(A)=&&&sizeof(A)&&//4
(内含一个int,普通函数不占大小)
cout&&&sizeof(B)=&&&sizeof(B)&&//8
(一个int ,一个虚函数表指针)
cout&&&sizeof(C)=&&&sizeof(C)&&//12
(一个int ,一个虚函数表指针,一个char ,再加上数据对齐)
cout&&&sizeof(D)=&&&sizeof(D)&&//8 (一个int ,一个虚函数表指针,多个虚函数是放在一个表里的,所以虚函数表指针只要一个就行了)
cout&&&sizeof(E)=&&&sizeof(E)&&//1
(static 函数不占大小,空类大小为1)
cout&&&sizeof(staticE)=&&&sizeof(staticE)&&//1
静态数据成员不计入类内
virtual void PrintA1(void){}
virtual void PrintA2(void){}
virtual void PrintB(void){}
virtual void PrintC(void){}
class cD : public cA, public cB, public cC
class cE : public cA, public cB, public cC
virtual void PrintE(void){}
void test2()
cout&&&sizeof(cD)=&&&sizeof(cD)&&//12 如果一个类里面什么也不实现,只实现一个或多个虚函数的话,测它的sizeof会得到4,但如果一个类从多个类继承,并且它的多个基类有虚函数的话,它就会有多个虚函数表了,这个在COM也有体现.
cout&&&sizeof(cE)=&&&sizeof(cE)&&//12
virtual ~dA(){}
class dB:virtual public dA
virtual void myfunB(){}
class dC:virtual public dA
virtual void myfunC(){}
class dD:public dB,public dC
virtual void myfunD(){}
void test3()
cout&&&sizeof(dA)=&&&sizeof(dA)&&//8
cout&&&sizeof(dB)=&&&sizeof(dB)&&//12
cout&&&sizeof(dC)=&&&sizeof(dC)&&//12
cout&&&sizeof(dD)=&&&sizeof(dD)&&//16
//解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。
//如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16. 注意此时虽然D的大小和虚继承一样,但是内存布局却不同。
class T2 : public T1
class T3 : public T1
virtual void GetM()=0;
virtual void GetM2()=0;
virtual void Get();
class M3 : public M1, public M2
void test4()
cout&&&sizeof(T2)=&&&sizeof(T2)&&//1
继承一个空父类,这时sizeof(父类)=0
cout&&&sizeof(T3)=&&&sizeof(T3)&&//4
cout&&&sizeof(M1)=&&&sizeof(M1)&&// 4
cout&&&sizeof(M2)=&&&sizeof(M2)&&// 4
cout&&&sizeof(M3)=&&&sizeof(M3)&&// 12
抽象函数与虚函数一样
void main()
/*虚函数表(http://www./c%2B%2B/.asp)
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
对C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
debug时,在局部变量窗口中可以看到,虚函数表
第七个例子:联合体的sizeof
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。
所以,下面例子中,U的sizeof值等于sizeof(s)。
三、sizeof深入理解。
1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
2.sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子:
char foo()
printf(&foo() has been called./n&);
return 'a';
int main()
size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof( char ),foo()并不会被调用
printf(&sizeof( foo() ) = %d/n&, sz);
sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。sizeof还可以用函数做参数,比如:
1.short&f();
2.printf(&%d\n&,&sizeof(f()));
输出的结果是sizeof(short),即2。
3. sizeof的常量性
sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如:
char ary[ sizeof( int ) * 10 ]; // ok
最新的C99标准规定sizeof也可以在运行时刻进行计算,如下面的程序在Dev-C++中可以正确执行:
n = 10; // n动态赋值
char ary[n]; // C99也支持数组的动态定义
printf(&%d/n&, sizeof(ary)); // ok. 输出10
但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。
4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。
char&ss[100]
10.sizeof(ss)
结果是100 ===》ss表示在内存中的大小 100×1
11.strlen(ss)
结果是10 ===》strlen是个函数内部实现是用一个循环计算到\0为止之前
5.大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
1.char&str[20]=&&;
2.int&a=strlen(str);&
3.int&b=sizeof(str);&
6.strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。
7.sizeof是算符,strlen是函数。sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
sizeof有三种语法形式,如下:
1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) // sizeof 对象;
sizeof( i ); // ok
sizeof( int ); // ok
既然写法3可以用写法1代替,为求形式统一以及减少我们大脑的负担,第3种写法,忘掉它吧!
实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。如:
sizeof( 2 ); // 2的类型为int,所以等价于 sizeof( int );
sizeof( 2 + 3.14 ); // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );
8.当适用了于一个结构类型时或变量, sizeof 返回实际的大小; 当适用一静态地空间数组, sizeof 归还全部数组的尺 寸。 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸
9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:
1.fun(char&[8])
2.fun(char&[])
都等价于 fun(char *) 在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小如果想在函数内知道数组的大小, 需要这样做:进入函数后用memcpy拷贝出来,长度由另一个形参传进去
1.fun(unsiged&char&*p1,&int&len)
3.unsigned&char*
buf =&new&unsigned&char[len+1]
4.memcpy(buf,
有关内容见: C++ PRIMER?
10.计算结构变量的大小就必须讨论数据对齐问题。为了CPU存取的速度最快(这同CPU取数操作有关,详细的介绍可以参考一些计算机原理方面的书),C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对齐(data alignment)。这样做可能会浪费一些内存,但理论上速度快了。当然这样的设置会在读写一些别的应用程序生成的数据文件或交换数据时带来不便。MS
VC++中的对齐设定,有时候sizeof得到的与实际不等。一般在VC++中加上#pragma pack(n)的设定即可.或者如果要按字节存储,而不进行数据对齐,可以在Options对话框中修改Advanced compiler页中的Data alignment为按字节对齐。
11.sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式
C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:
sizeof( foo ); // error
void foo2() { }
sizeof( foo2() ); // error
unsigned int f1 : 1;
unsigned int f2 : 5;
unsigned int f3 : 12;
sizeof( S.f1 ); // error
四、结束语
sizeof使用场合。
1.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如: 
1.void *malloc(size_t size), 
2.size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。
2.用它可以看看一类型的对象在内存中所占的单元字节。
1.void * memset(void * s,int c,sizeof(s))
3.在动态分配一对象时,可以让系统知道要分配多少内存。
4.便于一些类型的扩充,在windows中就有很多结构内型就有一个专用的字段是用来放该类型的字节大小。
5.由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
6.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有

我要回帖

更多关于 malloc 一个字节 的文章

 

随机推荐