windows 使用clang.exe编译c程序

  1. 编译好clang.exe;

—– 使用mingw编译链—

  1. …\bin\clang.exe hello.c -o aaa.exe –target=x86_64-pc-windows-gnu -mllvm -protect

—– 使用vs2017编译链——

  1. 到vs2017的安装目录搜索一个叫做vcvarsall.bat的文件. 可能的路径是:vs2017\VC\Auxiliary\Build运行 vcvarsall.bat  x86_amd64
  2. 然后运行以下命令编译c

clang.exe *.c –target=i686-pc-windows-vs2017  -o hanoi.exe

gcc同时使用动态和静态链接

场景是这样的。我在写一个Nginx模块,该模块使用了MySQL的C客户端接口库libmysqlclient,当然mysqlclient还引用了其他的库,比如libm, libz, libcrypto等等。对于使用mysqlclient的代码来说,需要关心的只是mysqlclient引用到的动态库。大部分情况下,不是每台机器都安装有libmysqlclient,所以我想把这个库静态链接到Nginx模块中,但又不想把mysqlclient引用的其他库也静态的链接进来。
我们知道gcc的-static选项可以使链接器执行静态链接。但简单地使用-static显得有些’暴力’,因为他会把命令行中-static后面的所有-l指明的库都静态链接,更主要的是,有些库可能并没有提供静态库(.a),而只提供了动态库(.so)。这样的话,使用-static就会造成链接错误。

之前的链接选项大致是这样的,

1 CORE_LIBS=”$CORE_LIBS -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto”

修改过是这样的,

1 2 CORE_LIBS=”$CORE_LIBS -L/usr/lib64/mysql -Wl,-Bstatic -lmysqlclient \ -Wl,-Bdynamic -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto”

其中用到的两个选项:-Wl,-Bstatic和-Wl,-Bdynamic。这两个选项是gcc的特殊选项,它会将选项的参数传递给链接器,作为链接器的选项。比如-Wl,-Bstatic告诉链接器使用-Bstatic选项,该选项是告诉链接器,对接下来的-l选项使用静态链接;-Wl,-Bdynamic就是告诉链接器对接下来的-l选项使用动态链接。下面是man gcc对-Wl,option的描述,

-Wl,option Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. You can use this syntax to pass an argument to the option. For example, -Wl,-Map,output.map passes -Map output.map to the linker. When using the GNU linker, you can also get the same effect with -Wl,-Map=output.map.

下面是man ld分别对-Bstatic和-Bdynamic的描述,

-Bdynamic -dy -call_shared Link against dynamic libraries. You may use this option multiple times on the command line: it affects library searching for -l options which follow it. -Bstatic -dn -non_shared -static Do not link against shared libraries. You may use this option multiple times on the command line: it affects library searching for -l options which follow it. This option also implies –unresolved-symbols=report-all. This option can be used with -shared. Doing so means that a shared library is being created but that all of the library’s external references must be resolved by pulling in entries from static libraries.

值得注意的是对-static的描述:-static和-shared可以同时存在,这样会创建共享库,但该共享库引用的其他库会静态地链接到该共享库中。

关于SIGSEGV错误及处理方法

关于SIGSEGV错误及处理方法
今天编程遇到了SIGSEGV错误,比较困惑,所以找了些资料,总结一下:

(1)官方说法是:
SIGSEGV — Segment Fault. The possible cases of your encountering this error are:

1.buffer overflow — usually caused by a pointer reference out of range.

2.stack overflow — please keep in mind that the default stack size is 8192K.

3.illegal file access — file operations are forbidden on our judge system.




(2)SIGBUS与SIGSEGV信号的一般区别如下:

1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。

2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。




(3)Linux的mmap(2)手册页

————————————————————————–
使用映射可能涉及到如下信号

SIGSEGV

试图对只读映射区域进行写操作

SIGBUS

试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。
————————————————————————–




弄清楚错误以后,就要查找产生错误的根源,一般我用以下两种方法:

(1)gcc -g 编译
ulimit -c 20000
之后运行程序,等core dump
最后gdb -c core <exec file>

     来查调用栈

(2)使用strace execfile,运行程序,出错时会显示那个系统调用错

转自:http://blog.csdn.net/brace/article/details/1102422

gcc编译常见问题解决方法

1、 implicit declaration of function `sleep’

#inlude<unistd.h>

 

2、 implicit declaration of function ‘malloc’

#include<stdlib.h>

3、 implicit declaration of function `memcpy’

#include <string.h>

4、 implicit declaration of function ` inet_addr’

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

5、 suggest parentheses around assignment used as truth value

=改成==

6、 Multiple definition of

将变量声明放到.c

mmap()—建立内存映射

相关函数:munmap, open

头文件:#include <unistd.h>      #include <sys/mman.h>

定义函数:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

函数说明:
mmap()用来将某个文件内容映射到内存中, 对该内存区域的存取即是直接对该文件内容的读写.

1、参数start 指向欲对应的内存起始地址, 通常设为NULL, 代表让系统自动选定地址, 对应成功后该地址会返回.

2、参数length 代表将文件中多大的部分对应到内存.

3、参数 prot 代表映射区域的保护方式有下列组合
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取

4、参数 flags 会影响映射区域的各种特性
MAP_FIXED 如果参数 start 所指的地址无法成功建立映射时, 则放弃映射, 不对地址做修正.通常不鼓励用此旗标.
MAP_SHARED 对应射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享.
MAP_PRIVATE 对应射区域的写入操作会产生一个映射文件的复制, 即私人的”写入时复制” (copy
on write)对此区域作的任何修改都不会写回原来的文件内容.
MAP_ANONYMOUS 建立匿名映射. 此时会忽略参数fd, 不涉及文件, 而且映射区域无法和其他进程共享.
MAP_DENYWRITE 只允许对应射区域的写入操作, 其他对文件直接写入的操作将会被拒绝.
MAP_LOCKED 将映射区域锁定住, 这表示该区域不会被置换(swap).

在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE.

5、参数fd 为open()返回的文件描述词,代表欲映射到内存的文件.

6、参数offset为文件映射的偏移量, 通常设置为0, 代表从文件最前方开始对应, offset必须是分页大小的整数倍.

返回值:若映射成功则返回映射区的内存起始地址, 否则返回MAP_FAILED(-1), 错误原因存于errno 中.

错误代码:
EBADF 参数fd 不是有效的文件描述词
EACCES 存取权限有误. 如果是MAP_PRIVATE 情况下文件必须可读, 使用MAP_SHARED 则要有
PROT_WRITE 以及该文件要能写入.
EINVAL 参数start、length 或offset 有一个不合法.
EAGAIN 文件被锁住, 或是有太多内存被锁住.
ENOMEM 内存不足.

范例
/* 利用mmap()来读取/etc/passwd 文件内容 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
main()
{
int fd;
void *start;
struct stat sb;
fd = open(“/etc/passwd”, O_RDONLY); //打开/etc/passwd
fstat(fd, &sb); //取得文件大小
start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if(start == MAP_FAILED) //判断是否映射成功
return;
printf(“%s”, start); munma(start, sb.st_size); //解除映射
closed(fd);
}

执行结果:
root : x : 0 : root : /root : /bin/bash
bin : x : 1 : 1 : bin : /bin :
daemon : x : 2 : 2 :daemon : /sbin
adm : x : 3 : 4 : adm : /var/adm :
lp : x :4 :7 : lp : /var/spool/lpd :
sync : x : 5 : 0 : sync : /sbin : bin/sync :
shutdown : x : 6 : 0 : shutdown : /sbin : /sbin/shutdown
halt : x : 7 : 0 : halt : /sbin : /sbin/halt
mail : x : 8 : 12 : mail : /var/spool/mail :
news : x :9 :13 : news : /var/spool/news :
uucp : x :10 :14 : uucp : /var/spool/uucp :
operator : x : 11 : 0 :operator : /root:
games : x : 12 :100 : games :/usr/games:
gopher : x : 13 : 30 : gopher : /usr/lib/gopher-data:
ftp : x : 14 : 50 : FTP User : /home/ftp:
nobody : x :99: 99: Nobody : /:
xfs 😡 :100 :101 : X Font Server : /etc/xll/fs : /bin/false
gdm : x : 42 :42 : : /home/gdm: /bin/bash
kids : x : 500 :500 :/home/kids : /bin/bash

stat()—获取文件状态

相关函数:fstat, lstat, chmod, chown, readlink, utime

头文件:#include <sys/stat.h>   #include <unistd.h>

定义函数:int stat(const char * file_name, struct stat *buf);

函数说明:stat()用来将参数file_name 所指的文件状态, 复制到参数buf 所指的结构中。

下面是struct stat 内各参数的说明:
struct stat
{
dev_t st_dev; //device 文件的设备编号
ino_t st_ino; //inode 文件的i-node
mode_t st_mode; //protection 文件的类型和存取的权限
nlink_t st_nlink; //number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
uid_t st_uid; //user ID of owner 文件所有者的用户识别码
gid_t st_gid; //group ID of owner 文件所有者的组识别码
dev_t st_rdev; //device type 若此文件为装置设备文件, 则为其设备编号
off_t st_size; //total size, in bytes 文件大小, 以字节计算
unsigned long st_blksize; //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小.
unsigned long st_blocks; //number of blocks allocated 占用文件区块的个数, 每一区块大小为512 个字节.
time_t st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、utime、read、write 与tructate 时改变.
time_t st_mtime; //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、utime 和write 时才会改变
time_t st_ctime; //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、权限被更改时更新
};

先前所描述的st_mode 则定义了下列数种情况:
1、S_IFMT 0170000 文件类型的位遮罩
2、S_IFSOCK 0140000 scoket
3、S_IFLNK 0120000 符号连接
4、S_IFREG 0100000 一般文件
5、S_IFBLK 0060000 区块装置
6、S_IFDIR 0040000 目录
7、S_IFCHR 0020000 字符装置
8、S_IFIFO 0010000 先进先出
9、S_ISUID 04000 文件的 (set user-id on execution)位
10、S_ISGID 02000 文件的 (set group-id on execution)位
11、S_ISVTX 01000 文件的sticky 位
12、S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限
13、S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限
14、S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限
15、S_IRGRP 00040 用户组具可读取权限
16、S_IWGRP 00020 用户组具可写入权限
17、S_IXGRP 00010 用户组具可执行权限
18、S_IROTH 00004 其他用户具可读取权限
19、S_IWOTH 00002 其他用户具可写入权限
20、S_IXOTH 00001 其他用户具可执行权限上述的文件类型在 POSIX 中定义了检查这些类型的宏定义
21、S_ISLNK (st_mode) 判断是否为符号连接
22、S_ISREG (st_mode) 是否为一般文件
23、S_ISDIR (st_mode) 是否为目录
24、S_ISCHR (st_mode) 是否为字符装置文件
25、S_ISBLK (s3e) 是否为先进先出
26、S_ISSOCK (st_mode) 是否为socket 若一目录具有sticky 位 (S_ISVTX), 则表示在此目录下的文件只能被该文件所有者、此目录所有者或root 来删除或改名.

返回值:执行成功则返回0,失败返回-1,错误代码存于errno。

错误代码:
1、ENOENT 参数file_name 指定的文件不存在
2、ENOTDIR 路径中的目录存在但却非真正的目录
3、ELOOP 欲打开的文件有过多符号连接问题, 上限为16 符号连接
4、EFAULT 参数buf 为无效指针, 指向无法存在的内存空间
5、EACCESS 存取文件时被拒绝
6、ENOMEM 核心内存不足
7、ENAMETOOLONG 参数file_name 的路径名称太长

范例
#include <sys/stat.h>
#include <unistd.h>
main()
{
struct stat buf;
stat(“/etc/passwd”, &buf);
printf(“/etc/passwd file size = %d \n”, buf.st_size);
}

执行:
/etc/passwd file size = 705

再说C/C++指针 | 一些C/C++指针的使用

#include <iostream>

using namespace std;

int main(void)

{

    int a = 10;//定义一个变量a,赋值为10

    int *ap =&a; //将a的地址给指针ap;”&”–取地址的符号;

    cout<<“a= “<<a<<“; *ap =”<<*ap<<endl;//输出指针ap地址里的内容

    (*ap) = 100;

    cout<<“a= “<<a<<“; *ap =”<<*ap<<endl;//输出指针ap地址里的内容

    cout<<“ap= &a =”<<ap<<” =”<<&a<<endl;//ap 与 &a 是同一地址

    /**

      *此时的指针ap地址就是变量a的地址,ap所指向的地址里的内容就是a的值;

      *改变指针ap地址里的内容就是在改变a的值。

      */

    int b =100;                        //定义一个变量a,赋值为10

    int *bp = (int*)malloc(sizeof(int));//为指针bp分配内存

    *bp = b;                            //只是把b的值赋给了指针bp所指地址里的内容

    cout<<“b= “<<b<<“; *bp =”<<*bp<<endl;//Notice:值相同,地址不同

    cout<<“bp!= &b; “<<“bp = “<<bp<<“;&b = “<<&b<<endl;

    (*bp) = 1000;                       //改变指针bp所指地址里的内容;它的改变与b无关

    cout<<“b= “<<b<<“; *bp =”<<*bp<<endl;

    cout<<“bp!= &b; “<<“bp = “<<bp<<“;&b = “<<&b<<endl;

    /**

      *这样一来(通过动态内存分配),指针具有一般变量与指针的双重功能;

      *总之,指针的功能显得很强大。

      */

    ///下面的就不合法了,你可以试试运行一下(肯定是错误的)

    //  int c = 1000;

    //  int *cp;

    // *cp = c;//无法赋值,因为指针cp没有指向明确的地址

    //cout<<*cp;//无法输出

    return 0;

}

代码段、数据段、堆栈段、数据段辨析

1、高位地址:栈(存放着局部变量和函数参数等数据),向下生长   (可读可写可执行)

2、           堆(给动态分配内存是使用),向上生长             (可读可写可执行)

3、           数据段(保存全局数据和静态数据)                    (可读可写不可执行)

4、地位地址:代码段(保存代码)                                (可读可执行不可写)

代码段就是存储程序文本的,所以有时候也叫做文本段,指令指针中的指令就是从这里取得。这个段一般是可以被共享的,比如你在Linux开了2个Vi来编辑文本,那么一般来说这两个Vi是共享一个代码段的,但是数据段不同(这点有点类似C++中类的不同对象共享相同成员函数)。

数据段是存储数据用的,还可以分成初始化为非零的数据区,BSS,和堆(Heap)三个区域。初始化非零数据区域一般存放静态非零数据和全局的非零数据。BSS是Block Started by Symbol的缩写,原本是汇编语言中的术语。该区域主要存放未初始化的全局数据和静态数据。还有就是堆了,这个区域是给动态分配内存是使用的,也就是用malloc等函数分配的内存就是在这个区域里的。它的地址是向上增长的。

最后一个堆栈段(注意,堆栈是Stack,堆是Heap,不是同一个东西),堆栈可太重要了,这里存放着局部变量和函数参数等数据。例如递归算法就是靠栈实现的。栈的地址是向下增长的。具体如下:

========高地址   =======
程序栈        堆栈段

向下增长

“空洞”       =======

向上增长


——          数据段
BSS
——
非零数据
=========低地址   =======

=========       =======
代码           代码段
=========       =======

需要注意的是,代码段和数据段之间有明确的分隔,但是数据段和堆栈段之间没有,而且栈是向下增长,堆是向上增长的,因此理论上来说堆和栈会“增长到一起”,但是操作系统会防止这样的错误发生,所以不用过分担心。

c++指针详解 | c++各种指针的使用 | c++中的引用

指针、引用与指针引用传值详解:(下文定义形式中的p都是标识符名,读者可自行修改)

1.指向常量的指针

可以改变指针指向哪个对象,但是不能改变指向对象的值

定义形式:
(const int*) p;
(int const*) p; //括号可以省去

测试内容:
——————–
int a = 10;
const int b = 2;

const int* test_p;

test_p = &b; //正确,指向常量的指针可以指向常量
test_p = &a; //正确,这里隐式转换:test_p = (const int *)&a;(如果是从const int* 转换到int或者const int 的话,需要显式转换)

*test_p = 3;//错误,不能尝试修改所指的对象的值
a++;
cout<<*test_p;//将输出11(上一语句虽然不能用*test_p++来修改所指向的对象a,但是由于a是变量,直接对变量进行修改后,指针值*test_p也就指向a++的值了)
//这种方法少用,造成用法不明确性
———————

//另加:
如果有
int *p;
const int b = 2;

p = &b;//错误,b是常量,普通指针不能指向常量,这是C++为了保证常量的只读性(毕竟如果p能指向b,那么根据定义*p的值就可以修改,这与常量不能重定义和修改矛盾)
———————

2.指针常量

可以改变指向对象的值,但不能改变指向的对象。

定义形式:
(int *const) p;//括号可以省去

测试内容:
———————
int a = 1;
int b = 2;
const int test_const = 3;

int* const test_p = &a; //必须初始化(这点与定义并初始化常量一样e.g: const int M = 0)

*test_p = 3; //正确,可以修改
test_p = &b;//错误,不能尝试修改所指对象
———————-

//另外
//若在初始化时按以下操作
int* const test_p = (int *) &test_const; //必须显示(int *)转换,若没有的话,编译器会报错(毕竟test_p是int类型,而不是const int,若所指的对象为const int,则 *test_p = 3的操作会与常量不能重定义和修改矛盾)
———————-

3.指向常量的指针

结合以上两个,只能在定义时初始化,之后不能修改所指对象,也不能修改所指对象的值,对上述的两个另外也需要做一些调整,这里就不说了。

定义形式:
const int* const p;

测试内容:
———————-
int a = 1;
const int b = 2;

const int* const test_p0 = &a;//正确,可以指向变量
const int* const test_p1 = &b;//正确,可以指向常量

test_p0 = &b; /* or */ test_p1 = &a; //都错误,不能改变指向对象
*test_p0 = 0; /* or */ *test_p1 = 0; //都错误,不能改变指向对象的值

//注意
const int* const test_p0 = &a;
//上面初始化后,对a进行a++
a++;
//则输出的*test_p0为2
cout<<test_p0; //输出结果为2
———————–

4.引用

引用相当于一个对象的昵称,如我名字叫小明,外号叫牛哥,以“小明”“牛哥”叫我都是一样,我独自共享两个“名字”,故引用只占一个变量的空间,与指针不同,指针声明时需要开辟新的内存空间来。对象名和引用名是困捆绑在一起。

定义形式:
int& p = a;//一定要在定义的时候初始化值

测试内容:
————————
int a = 0;
int& b = a;

b++;/* or */ a++;//都正确,任何一个的值修改都会改变另一个的值(而且cout<<&b<<&a; 都是同一个地址值,而指针中int* p = &a; cout<<&p<<&a;是两个不同的地址值)

//如果是const 引用的话:
const int& b = a;
b++;//错误,常引用(相当于常量)不能作修改。
————————-

引用和常引用通常用来作函数传值参数,不占用内存,效率快一些。
指针传值和引用传值都会引起传入的参数数值的改变(除了指向常量的指针和常引用),具体用法请自行上机尝试一下。

C,C++内存分配的详细讲解包括堆,栈,数据段等

一. 在c中分为这几个存储区
1.栈 – 由编译器自动分配释放
2.堆 – 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.另外还有一个专门放常量的地方。- 程序结束释放

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在 所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函 数中的”adgfdf”这样的字符串存放在常量区。比如:

int a = 0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[] = “abc”; //栈
char *p2; //栈
char *p3 = “123456”; //123456{post.content}在常量区,p3在栈上
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10); //分配得来得10字节的区域在堆区
p2 = (char *)malloc(20); //分配得来得20字节的区域在堆区
strcpy(p1, “123456”);
//123456{post.content}放在常量区,编译器可能会将它与p3所指向的”123456″优化成一块
}

二.在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)

三. 谈谈堆与栈的关系与区别
具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出 栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特 点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作, 而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自 动变量自动失效的原因。

和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free 函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空 间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内 存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分 配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:
1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。
2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。
3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。

堆和栈的对比
从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而栈是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有 一定降低。栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。栈空间分静态分配和动态分配 两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函 数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/ 释放内存匹配是良好程序的基本要素。

1.碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问 题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详 细的可以>参考数据结构,这里我们就不再一一讨论了。
2.生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
3.分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
4.分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效 率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统) 在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机 会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

明确区分堆与栈:
在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。
首先,我们举一个例子:

void f()
{
int* p=new int[5];
}

这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思 就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:
00401028    push         14h
0040102A    call            operator new (00401060)
0040102F    add           esp,4
00401032    mov          dword ptr [ebp-8],eax
00401035    mov          eax,dword ptr [ebp-8]
00401038    mov          dword ptr [ebp-4],eax
这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。
好了,我们回到我们的主题:堆和栈究竟有什么区别?
主要的区别由以下几点:
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:
打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存 的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈 的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。

另外对存取效率的比较:
代码:

char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;

aaaaaaaaaaa是在运行时刻赋值的(位于栈上);
而bbbbbbbbbbb是在编译时就确定的(位于堆上);
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:

void main()
{
char a = 1;
char c[] = “1234567890”;
char *p =”1234567890″;
a = c[1];
a = p[1];
return;
}

对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了.
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就 算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,编写稳定安全的代码才是最重要的

static用来控制变量的存储方式和可见性

函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。

需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

static的内部机制:

静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。

这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。

静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。

static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

static的优势:

可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

引用静态数据成员时,采用如下格式:

<类名>::<静态成员名>

如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。

PS:

(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

(2)不能将静态成员函数定义为虚函数。

(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based XWindow系统结合,同时也成功的应用于线程函数身上。

(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。

(6)静态数据成员在<定义或说明>时前面加关键字static.

(7)静态数据成员是静态存储的,所以必须对它进行初始化。

(8)静态成员初始化与一般数据成员初始化不同:

初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;初始化时不加该成员的访问权限控制符private,public等;

初始化时使用作用域运算符来标明它所属类;

所以我们得出静态数据成员初始化的格式:

<数据类型><类名>::<静态数据成员名>=<值>

(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有 重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

补充:new delete[],基本类型的对象没有析构函数(例如 int,char),所以回收基本类型组成的数组空间delete delete[] 都是应该可以如:int p = new int[10], delete p 和delete[]p 都可 .但是对于类对象数组(如string strArr = new string[10]),只能 delete[].对 new 的单个对象,只能 delete 不能 delete[] 回收空间.

一个典型的嵌入式平台动态内存管理机制

http://www.elechome.com/Article/ShowArticle.asp?ArticleID=170

当前,绝大多数嵌入式平台上的软件都采用C语言编写。除了代码简洁、运行高效之外,灵活操作内存的能力更是C语言的重要特色。然而,不恰当的内存操 作通常也是错误的根源之一。如“内存泄漏” ——不能正确地释放已分配的动态内存,就是一种非常难于检测的存错误。持续的内存泄漏会使程序性能下降到最终完全不能运行,进而影响到所有其它有动态内存 需求的程序,在某些相对简单的嵌入式平台上甚至会妨碍操作系统的运转。再如“写内存越界”,一种不合法的写内存操作,极可能破坏到本程序中正在使用的其它 数据,严重的时候还可能对其它正在运行的程序甚至整个系统造成影响。为此,本文介绍一个增强的、可定制的动态内存管理模块(以下不妨简称Fense),在 C语言提供的内存分配函数基础上,增加了对动态内存的管理功能;能记录软件运行过程中出现的内存泄漏信息,同时也具一定的监测内存操作的能力;可以发现绝 大多数对动态内存的写越界错误。

Fense的设计原理

通过设立一个双向链表(struct Head *stHead)来保存所有被分配的动态内存块的信息。链表中的每个节点对应一个动态内存块,节点中包括此内存大小、分配发生时所在的源文件名和行号以及 被释放的时候,Fense又从st_Head中删除之,检查st_Head中的节点即可得到未被释放的本节点的数值校验和等。Fense将每一个分配的动 态内存块插入到链表st_Head中;当此内存放内存块信息。链表节点结构定义如下:

struct Head{

char file; /分配所在源文件名*/
unsigned long line; /*分配所在的行号*/
size_t size; /*分配的内存大小*/
int checksum; /*链表节点校验和*/
struct Head prev,next; /*双链表的前后节点指针*/
};

/*全局的双向链表*/
struct Head *st_Head=NULL;

为了检测写越界的错误,Fense在用户申请的内存前后各增加了一定大小的内存作为监测区域,并初始化成预定值。这样,当程序发生越界写操作时,预定值就会发生改变,Fense即可检测到错误。

Fense的具体实现
Fense 提供Fense_Malloc、Fense_Free、Fense_Realloc及Fense_Calloc等内存管理函数,功能和调用形式与C语言中 的malloc、free、realloc和calloc保持一致。限于篇幅,这里仅对Fense_Malloc和Fense_Free的实现过程做一个 简单描述,

/*内存分配函数*/
void *Fense_Malloc(size_t size,char *file,unsigned long line)
{
//检查Fense的运行时开关,如果Fense被关闭,则调用malloc
//分配并返回
//检查是否零分配,如有则提示警告信息后返回0(用户定制选项)
//分配内存,包括链表节点区域和前/后监测区域
//初始化链表节点,保存分配内存的信息,包括分配的大小、所在文件名和行号
//将此节点插入链表st_Head
//为本节点区域计算校验和
//用预设值初始化前/后监测区域
//用预设值填充用户内存区域(用户定制选项)
//返回用户内存区域的起始位置
}
/*内存释放函数*/
void Fense_Free(void *uptr,char *file,unsigned long line)
{
//检查Fense的运行时开关,如果Fense初关闭,则调用free释译并返回
//检查所有Fense管理下的动态内存(用户定制选项)
//判断当前内存块是否在链表st_Head中,如果不在则提示
//警靠信息,退出(用户定制选项)
//检查当前内存块是否存在越界操作
//将当前内存块的相应的链表节点从st_Head中删除
//重新计算当前节点的前后相邻节点的校验和
//用预设值填充被释放的内存区(用户定制选项)
//调用free释放当前的内存块
}

用户态与核心态

386及以上的CPU实现了4个特权级模式(WINDOWS只用到了其中两个),其中特权级0(Ring0)是留给操作系统代码,设备驱动程序代码 使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由 地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页 面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特 权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种实模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。既然运行 在核心态有如此之多的优势,那么病毒当然没有理由不想得到Ring0。处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访 问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操 作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模 式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将 结果传给用户程序。

用户态又称目态,核心态又称管态
在X86下,可以理解成ring3 和 ring 0
用户态权限低,无权调用一些核心态才能调用的指令

这是两种内存保护态,一个进程4G地址空间中的每一页均被标记出它是否是处于核心态,所有系统地址空间中的页是核心态,用户空间中的页则为用户态。
访问标记为核心页的的唯一途径是运行在核心态,而只有操作系统和设备驱动才能运行在核心态。
因此一个应用程序不能使自己运行在核心态中,这样为应用程序和操作系统提供了内存保护的坚固级别。用户态怎么修改都不能让系统崩溃。当然应用程序可以通过加载设备驱动进入核心态,去修改系统数据。

MFC中,消息传递是在用户态完成的。同步对象大部分在核心态,只有CriticalSection可以在用户态工作(也可能进入核心态),所以CriticalSection是不能用于进程间同步的。