奇怪的死循环

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int i;
int a[10];
for(i=0;i<=10;++i)
{
a[i]=0;
printf("%d\n",a[i]);
}
return 0;
}

该程序对应的汇编代码见如下代码:

C++对象模型-关于对象

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,关于对象

从这篇博客开始真正介绍C++对象模型,前边BB了那么多没用的,终于开始了C++对模型的分析。关于C++对象模型的介绍,我将根据《深度探索C++对象模型》这本书,其书中的每一章,对应一篇博客,博客内容为自己对这本书的理解和补充吧。

1.1C语言中的struct

我们知道,C语言是面向过程的,即数据和处理数据的函数时分开的,也就是说,struct中不能包含函数(当然也不能包含static变量)。但是我们可以在struct中声明指向函数的指针来模拟数据和处理数据的函数指针。其中,需要指出的是,c语言中struct变量的数据成员的访问方式都是基于该结构体变量的首地址的偏移量来访问的(同样适用于C++中的class/struct)。
即,见如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>

struct point3d;
typedef void (*initfun)(struct point3d*);

struct point3d{
float x;
float y;
float z;
initfun init;//指向初始化数据函数的指针
};
void initp(struct point3d* p3d) //struct point3d变量初始化函数
{
p3d->x=1;
p3d->y=2;
p3d->z=3;
printf("%f %f %f\n",p3d->x,p3d->y,p3d->z);
return;
}

int main()
{
struct point3d pd;
pd.init=initp;//将initp函数地址赋给结构体中指向函数的指针pd.init。
pd.init(&pd);
}

C++对象模型-3

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,switch/if-else

上一节 数组的内存布局。本文准备介绍一下switch/if-else的实现机制。

1.1swich实现机制
1.2 if-else实现机制
  • 待续

基础类型的内存表示

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,基础类型内存表示

上一节 介绍了swich/if-else的实现机制。本文准备介绍一下基础类型的内存表示。

1.1大端法和小端法

小端法,即数字的低位在低地址。大端法,即数字高位在低地址。大小端法的最小单位为byte,而不是bit.
举例:

1
2
3
4
5
int i=-16;
对应的小端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0xf0 ff ff ff
大端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0x FF FF FF F0

在实际开发中,可以需要做大小端法互换,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void swap1(int *rhs)
{
unsigned char *p=rhs;
unsigned char tmp;
tmp=p[0];
p[0]=[3];
p[3]=tmp;

tmp=p[1];
p[1]=[2];
p[2]=tmp;
return ;
}

在小端机上,如下程序的输出结果为-16.

1
2
3
4
5
6
7
8
9
10
int main()
{
int i;
unsigned char *p=(unsigned char*)&i;
p[0]=0xF0;
p[1]=0XFF;
p[2]=0XFF;
p[3]=0Xff;
printf("%d\n",i);
}

动态/静态数组内存布局

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,数组内存布局

上一节 简单介绍了结构体作为函数参数和返回值的情况。本文准备介绍一下数组的内存布局,即静态数组/动态数组和一维数组/二维数组,顺便介绍一下0长度数组的妙用。

1.1静态一维数组和动态二维数组

静态一维数组,即类似于int a[10];动态数据,即类似于int p=(int)malloc(10sizeof(int));(或者int p=new int[10]);

1.1.1静态一维数组
1
2
3
4
5
6
7
8
9
10
#include<stdio.h>

int main()
{
int a[3];
a[0]=1;
a[1]=2;
a[2]=3;
printf("%d %d %d\n",a[0],a[1],a[2]);
}

对应的汇编代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
	.file	"c2-0.c"
.section .rodata
.LC0:
.string "%d %d %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $1, 20(%esp) //esp+20即为p[0]的地址
movl $2, 24(%esp) //esp+24即为p[1]的地址
movl $3, 28(%esp) //esp+28即为p[2]的地址
movl 28(%esp), %ecx
movl 24(%esp), %edx
movl 20(%esp), %eax
movl %ecx, 12(%esp) //将ecx压栈,即[esp+12]=ecx
movl %edx, 8(%esp) //将edx压栈,即[esp+8]=edx
movl %eax, 4(%esp) //将eax压栈,即[esp+4]=eax
movl $.LC0, (%esp) //类似于“%d %d”字符串的地址
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits

main函数执行时,栈帧为:
img0

结构体变量作为函数参数和返回值

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,结构体类型作为函数参数和返回值

上一节 简单介绍了基本的函数调用过程,即栈帧。本节介绍结构体类型作为函数参数和返回值的情况。

1.1结构体变量的内存布局

结构体变量的数据成员之间的内存空间是连续的,结构体中的每个成员都是通过相对于结构体变量首地址的偏移量来访问。假设有如下结构体:

1
2
3
4
5
6
7
8
9
10
11
12
struct test{
char k;
int i;
short j;
};
如果想获得k变量的偏移量可以通过如下方式:
struct test* p=NULL;
(unsigned int)&(p->k);
等价于: (unsigned)(&(((struct test*)0)->k));
或者等价于:
struct test x;
(unsigned int)((unsigned char)&(x.k)-(unsigned char)&(x));

struct test x;其中x结构体变量的内存布局为:
其中空白表示该字节为对齐字节,即没有用到;i0表示i变量的第一个字节,以此类推。
struct

C++对象模型-目录

研究生马上要毕业了,至今感觉一事无成。在毕业之前,希望写一些真正留在自己心里的东西。接下来准备每天写一篇用汇编分析一下C语言和C++对象模型的博客,也算是给自己的大学生涯画上一个句号。

TODO:

准备开始几天先介绍下C语言的东西(自己也努力回忆下汇编语言,参看《CSAPP》第3章),如栈帧,基础类型的内存布局/结构体变量的内存布局,结构体变量作为函数参数/作为函数返回值,switch/if-else实现机制,变长参数实现机制,二维数组内存布局等,然后再对C++进行深入分析,如C++对象内存布局,引用/指针,构造/析构函数的执行过程,多继承类对象的内存布局,虚继承类对象的内存布局,C++对象作为函数参数/作为函数返回值等。

1,目录

  1. C++对象模型-0 stack frame
  2. C++对象模型-1 struct
  3. C++对象模型-2 array
  4. C++对象模型-4 int/float/Big-Endian/Little-Endian
  5. C++对象模型-5 Object Lessons

2,参考文献

《深度探索C++对象模型》
《CSAPP中文第二版》
《程序员的自我修养》
《C++反汇编与逆向分析技术揭秘》
《老码识途》
《Computer Systems A Programmer’s Perspective THIRD EDITION》

函数调用过程(栈帧)

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,函数调用过程

今天先介绍下基本的函数调用过程,即栈帧。

1.1栈帧

每个函数调用都对应一个栈帧。每个栈帧由ESP和EBP寄存器来确定。每个函数执行时,其局部变量都是在自己对应的栈帧内分配内存。假设A函数调用B函数,此时正在执行B函数,需要指出的是,当执行完当前函数B后,返回调用函数A,此时执行函数B时,为B函数的局部变量分配的的内存空间也就不存在了。也就是说,函数返回值不能是函数体内局部变量的地址,也不能是局部变量的引用。即如不能出现如下两种形式之一:

24 bits解析为有符号整数

最近遇到了24bits来解析为有符号整数的问题,提供如下两个解决思路:

方法1:

24bits即从高位到低位为:buf[0],buf[1],buf[2] 三个bytes;通过将最高字节buf[0]符号扩展为两个字节(假设从高位到低位为ps[1],ps[0]),然后将ps[1],ps[0],buf[1],buf[2]拼接为int变量的四个字节;见下图;
pic1

Linux内存流

1, 内存流

As we’ve seen, the standard I/O library buffers data in memory, so operations such as character-at-a-time I/O and line-at-a-time I/O are more efficient. We’ve also seen that we can provide our own buffer for the library to use by calling setbuf or setvbuf. In Version 4, the Single UNIX Specification added support for memory streams. These are standard I/O streams for which there are no underlying files, although they are still accessed with FILE pointers. All I/O is done by transferring bytes to and from buffers in main memory.

1
2
3
4
#include <stdio.h>     
FILE * fopen(const char * path,const char * mode);
FILE *fmemopen(void *restrict mem, size_t size, const char *restrict type);
Returns: stream pointer if OK, NULL on error

其中mem相当于fopen函数中的文件(即参数path);如果mem参数为空,则fmemopen函数分配size字节数的内存(我们称作“anonmem”,当流关闭时anonmem会被释放)。
其中type参数取值的含义:

  1. whenever a memory stream is opened for append, the current file position is set to the first null byte in the buffer. If the buffer contains no null bytes, then the current position is set to one byte past the end of the buffer. When a stream is not opened for append, the current position is set to the beginning of the buffer. Because the append mode determines the end of the data by the first null byte, memory streams aren’t well suited for storing binary data (which might contain null bytes before the end of the data).
  2. a null byte is written at the current position in the stream whenever we increase the amount of data in the stream’s buffer and call fclose, fflush, fseek,fseeko, or fsetpos.(只有mem中的数据量增加(条件1)并且调用fclose,fflush,fseek,fseeko,fsetpos函数时(条件2),当前位置才会被写入一个null字节.如果条件1和条件2只有一个成立,则不会插入null)。