随笔 - 14, 文章 - 2, 评论 - 1, 引用 - 0
数据加载中……

2015年7月14日

如何在Visual Studio项目中正确添加汇编代码

以下内容,从“http://blog.csdn.net/blog_index/article/details/6878166”引用过来。 

1.      问题描述


在以往的编程经历中,本人最常使用的汇编代码是__asm {int 3}。它可以在我的代码中插入一个软件断点。如果没有一个连接到当前程序的调试器,则程序将停止在这行语句处无法继续执行。

上面的这种方式称为高级语言和汇编语言混编。当我用得正爽的时候,迎头碰到了64位平台这员猛将。它大手一张,挑出一张禁令通知单:在64位编程中,高级语言和汇编混编将再也不被支持!兄弟我不禁暗暗垂泪,因为当我固执而行的时候,若干恶心的编译错误赫然出现在我的面前:

  1. 1>test.cpp(98):error C4235: nonstandard extension used : '__asm' keyword not supported on thisarchitecture  
  2. 1>test.cpp(98):warning C4091: '' : ignored on left of 'int' when no variable is declared  
  3. 1>test.cpp(98):error C2143: syntax error : missing ';' before 'constant'  
 上面的几行所示的并非什么惊天动地的大错误,意思非常简单:当前架构不支持__asm关键字。

既然被踢出了体制外,我们要想生存,就必须得另谋出路。


 2.      使用.asm文件


在64位平台上,我们遇到了翩然而至的.asm文件。在和她共舞的过程中,下面几个步骤必须遵守:

  1. 使用.asm文件包含汇编代码;.asm文件以关键字.CODE开始,关键字END结束
  2. 所有的汇编代码以函数方式组织在一起,一个典型的函数声明如下:

 

  1. FunName PROC  
  2.     // 此处包含汇编指令   
  3. FunName ENDP  
这里面有两个关键字分别表示函数的开始和结束:PROC和ENDP。我们看一个简单的包含中断指令的函数应当怎么写。我们假设一个.asm文件中仅有一个函数实现,下面是一个.asm汇编源文件的全部内容:
  1. //   
  2. // FILE: test.asm   
  3. // Description: 汇编源文件。当前文件仅在x64平台上编译,并不包含于x86平台,可通过设置源文件的属性实现。   
  4. //    
  5.   
  6. .CODE // 文件开始   
  7.   
  8. // 函数原型: void Int_3()   
  9. // 函数描述:函数实现中断指令,没有输入/输出参数,也没有返回值   
  10. Int_3 PROC  
  11.        int 3 // 中断指令   
  12.        ret   // 函数返回指令   
  13. Int_3 ENDP  
  14.   
  15. END  // 文件结束  

这个文件名为test.asm,文件包含名为Int_3的汇编函数,函数本身仅有两行代码:中断指令,返回指令。


 3.      X86与X64的兼容


现在澄清一下状况:

  1. 在X86平台上,我们应该使用混编方式执行汇编指令;
  2. 在X64平台上,我们应该使用.asm添加汇编函数。

 

为了让一段包含汇编指令的代码能够同时在X86和X64平台上编译通过,我们要有办法让编译器自己判断硬件平台。编译器提供了名为_M_AMD64的宏,只有在X64平台上,这个宏才是被定义的。所以我们可以用类似下面的语法进行编程:

  1. #ifdef _M_AMD64   
  2.   // 这是x64平台   
  3. #else   
  4.   // 这是x86平台   
  5. #endif  

实际上,大多数软件都不使用_M_AMD64宏,而是使用在windows.h中定义的另一个表达同一含义的名为_AMD64_的宏。所以上文的另一种写法如下:

 

  1. #include <windows.h>   
  2.   
  3. #ifdef _AMD64_   
  4.   // 这是x64平台   
  5. #else   
  6.   // 这是x86平台   
  7. #endif  
如果我们只想在程序中添加一个软中断指令,则完整的实现示例如下:

 

  1. #include <windows.h>   
  2.   
  3. extern "C" void Int_3();  
  4.   
  5. int _tmain()  
  6. {  
  7. #ifdef _AMD64_   
  8.    printf("这是x64平台,使用.asm汇编文件中定义的中断函数\n");  
  9.    Int_3();  
  10. #else   
  11.    printf("这是x86平台,使用__asm混编方式插入中断指令\n");  
  12.    __asm int 3;  
  13. #endif   
  14.   
  15.   return 0;  
  16. }  

 

4.      Visual Studio编译


将.asm文件添加到项目中后,如果不做任何操作即直接编译,我们会惊奇地发现.asm文件被编译器无视了(未被编译)。为弄清个中缘由,请在.asm文件上右击并选择属性,在弹出的属性对话框中,你是否看到如下设置?

 

由于这个文件的"项类型"被默认设置为“不参与生成”,所以在编译的时候,编译器就不会带上它。不晓得这一点在新的Visual Studio里面会不会有改正。这时候各位也不要慌了手脚,Visual Studio看上去对.asm这个灰头土脸的家伙有点不客气,觉得它好面生,所以有什么活动都不愿意带上它。没有关系,让我们想办法来让它们熟悉起来吧。

  1. 首先,确保你当前的平台是x64平台。切记不要对x86平台下的.asm文件做任何修改,因为.asm文件在x86平台上是无法编译的(目前为止,据我所知)。
  2. 其次,请确保“从生成中排除”项的值是“否”。
  3. 第三,让我们点击"项类型"的下标箭头,会看到如下所示的众多可选项:

 


第一个选项“C/C++编译器”,是用来编译.C/.cpp文件的,如果点击一个.cpp文件看到的就会是它被默认选中。但汇编文件不能使用C/C++编译器,读者不信可以试一试,一定有乱七八糟的编译错误秀出来。正确的做法是选择“自定义生成工具”项,点击“应用”后左列将出现名为“自定义生成工具”的子项,如下图所示:


在右列面板中,我们有一些设置需要添加进去,上图中已由红色方框标出。

  1. 首先在“命令行”项中,我们应当输入指定的编译语句:ml64 /c %(fileName).asm。这条命令的意思是使用ml64.exe.asm文件进行编译,这里面的宏%(FileName)用来代指当前文件(test.asm)。命令行中的ml64乃指Ml64.exe编译程序,它在Visual Studio的默认执行路径中。把这条执行语句直接放在控制台里面执行,一样是能够通过的。
  2. 在“输出”项中,我们设定输出文件为:%(fileName).obj。即编译后所生成的中间文件,而宏%(filename)含义如上。

 

确定并退出属性对话框后,再次尝试,会发现x86和x64平台下的编译都能够成功了。我们通过上述方法,成功地在C/C++项目中添加汇编指令,并能够在32/64位平台上顺利编译通过。虽然我还没有亲自试验,但相信相同的方法也能够使用于.net等其他编程语言。

posted @ 2015-07-14 11:51 Jeremy1111 阅读(2605) | 评论 (0)编辑 收藏

2014年11月5日

x64 Assembly

引用“http://blog.sina.com.cn/luckdonkey ”

全局变量保存在数据段中,即伪代码中声明的DATA SEGMENT,任何时刻都能调用:而局部变量则保存在栈段中,是伪代码中声明的STACK SEGMENT,平时不调用,调用中断时把局部变量从栈里取出来

局部变量: 不过局部变量是在你用到的时候进行初始化,占用内存空间
局部变量的作用域是单个子程序,在进入子程序的时候,通过修改栈指针esp来预留出需要的空间,在用ret指令返回主程序之前,同样通过恢复esp丢弃这些空间,这些变量就随之无效了.它的缺点是因为空间是临时分配的,所以无法定义含有初始化值的变量,对局部变量的初始化一般在子程序中同指令完成

定义格式:
local  
变量名1[[重复次数]] [:类型], 变量名2[[重复次数]] [:类型]…
local
伪指令必须紧接在子程序定义的伪指令proc,其他指令开始前.
1:
可以有多个local语句.
2:
不能用类型缩写.
3:
定义结构体,可以用结构体的名称当做类型.
4:
定义dword类型的局部变量,类型可以省略
5:
定义数组时可以用[]括起来.
6:
不能使用定义全局变量的dup伪指令.
7:
不能和已经定义的全局变量重名.
8:
局部变量的起始值是随机的,所以局部变量的值一定要初始化
X64提供了两类新的寄存器:

  •  8个通用寄存器:r8 – r15
  • 8128位的XMM寄存器:xmm8 – xmm15

另外,在x86架构下的寄存器则从32位扩展到了64位:rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp rip。它们的64位形式的名称前都一个“r”前缀。老的寄存器还可以使用,可存取位数小一些的数据,比如

Bits

64

32

16

8

8

名称

rax

eax

ax

ah

Al


新的寄存器可以这样使用:

Bits

64

32

16

8 low

名称

r8

r8d

r8w

r8b

程序仍然可以用段寄存器来进行基本的地址处理,但在64位模式下只认3个寄存器,csfsgs,只有fsgs可以用来做基本的地址计算。

32位下的ex64调用约定

x64的调用约定也被称为x64 ABI(应用程序二进制接口),它描述调用者和函数间的接口:

  • 分配参数的顺序
  • 参数放置的位置(置于栈上还是置于寄存器中)
  • 函数可使用哪些寄存器
  • 返回时如何恢复堆栈

X64的调用约定跟x86fastcall调用约定很相像,传参数给函数时,把寄存器和堆栈联合起来使用。

1.1 整数,指针和引用参数

  • 所有寄存器中的参数都是右对齐的,这样被调用者就能按需要忽略寄存器的高位,按照需要存取寄存器的一部分。
  • 所有栈上的参数都是8字节对齐。
  • 所有的不是1248字节的参数(包括结构)都通过引用传递。
  • 8163264比特的结构和联合如果跟整数一样长时也可以传递。

4个整数参数按照从左到右的顺序用rcxrdxr8r9传递

剩下的参数用栈传递,压栈的顺序是从右到左(右边的参数在更低的位置)。

虽然这里不能详述,但是类的成员函数用汇编函数写出来,这时this指针让在rcx中传递。

1.2 浮点数(FP)参数

  • 4个浮点参数按照从左到右的顺序通过xmm0xmm3传递
  • 剩下的浮点参数按照从右到左的顺序压栈进行传递(越靠左的参数在栈上的地址越低)。
  • x87register stack.不能使用

1.3 返回值

  • 整数,指针和引用类型通过rax传回。
  • 浮点数用xmm0返回。

1.4 易变性和非易变性

  •   函数中必须保留的:rbx, rbp, rdi, rsi, r12, r13, r14, r15, xmm6 - xmm15 x87 register stack.
  •  函数中可以破坏使用的:rax, rcx, rdx, r8, r9, r10, and r11 xmm0 - xmm5

3.5

调用者必须至少保留32字节(464比特的值)的栈,这些空间可以让传进函数的寄存器很容易拷贝(泄露)到知名的栈位置去。函数不是必须要泄漏寄存器参数到栈上去,但是堆栈空间保留机制保障在需要的时候可以做到。

1.6 堆栈清理

调用者负责清理堆栈,一般情况是,函数需要足够多的栈空间,调用者保留足够的栈空间给函数,并调整栈空间的位置以满足它调用的所有函数。

ip寄存器在64位下变成了rip寄存器。


posted @ 2014-11-05 14:06 Jeremy1111 阅读(575) | 评论 (0)编辑 收藏

2010年7月21日

Linux技巧:多核下绑定硬件/进程到不同CPU

件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。现在的服务器上动不动就是多 CPU 多核、多网卡、多硬盘,如果能让网卡中断独占1个 CPU (core)、磁盘 IO 中断独占1个 CPU 的话将会大大减轻单一 CPU 的负担、提高整体处理效率。我前天收到一位网友的邮件提到了 SMP IRQ Affinity,引发了今天的话题。以下操作在 SUN FIre X2100 M2 服务器+ 64位版本 CentOS 5.5 + Linux 2.6.18-194.3.1.el5 上执行。

什么是中断

中文教材上对 “中断” 的定义太生硬了,简单的说就是,每个硬件设备(如:硬盘、网卡等)都需要和 CPU 有某种形式的通信以便 CPU 及时知道发生了什么事情,这样 CPU 可能就会放下手中的事情去处理应急事件,硬件设备主动打扰 CPU 的现象就可称为硬件中断,就像你正在工作的时候受到 QQ 干扰一样,一次 QQ 摇头就可以被称为中断。

中断是一种比较好的 CPU 和硬件沟通的方式,还有一种方式叫做轮询(polling),就是让 CPU 定时对硬件状态进行查询然后做相应处理,就好像你每隔5分钟去检查一下 QQ 看看有没有人找你一样,这种方式是不是很浪费你(CPU)的时间?所以中断是硬件主动的方式,比轮询(CPU 主动)更有效一些。

好了,这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?这个很容易,不是每个 QQ 号码都不相同吗?同样的,系统上的每个硬件设备都会被分配一个 IRQ 号,通过这个唯一的 IRQ 号就能区别张三和李四了。

在计算机里,中断是一种电信号,由硬件产生,并直接送到中断控制器(如 8259A)上,然后再由中断控制器向 CPU 发送信号,CPU 检测到该信号后,就中断当前的工作转而去处理中断。然后,处理器会通知操作系统已经产生中断,这样操作系统就会对这个中断进行适当的处理。现在来看一下中断控制器,常见的中断控制器有两种:可编程中断控制器 8259A 和高级可编程中断控制器(APIC),中断控制器应该在大学的硬件接口和计算机体系结构的相关课程中都学过。传统的 8259A 只适合单 CPU 的情况,现在都是多 CPU 多核的 SMP 体系,所以为了充分利用 SMP 体系结构、把中断传递给系统上的每个 CPU 以便更好实现并行和提高性能,Intel 引入了高级可编程中断控制器(APIC)。

光有高级可编程中断控制器的硬件支持还不够,Linux 内核还必须能利用到这些硬件特质,所以只有 kernel 2.4 以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的 CPU 上,这个绑定技术被称为 SMP IRQ Affinity. 更多介绍请参看 Linux 内核源代码自带的文档:linux-2.6.31.8/Documentation/IRQ-affinity.txt

如何使用

先看看系统上的中断是怎么分配在 CPU 上的,很显然 CPU0 上处理的中断多一些:

# cat /proc/interrupts
CPU0       CPU1
0:  918926335          0    IO-APIC-edge  timer
1:          2          0    IO-APIC-edge  i8042
8:          0          0    IO-APIC-edge  rtc
9:          0          0   IO-APIC-level  acpi
12:          4          0    IO-APIC-edge  i8042
14:    8248017          0    IO-APIC-edge  ide0
50:        194          0   IO-APIC-level  ohci_hcd:usb2
58:      31673          0   IO-APIC-level  sata_nv
90:    1070374          0         PCI-MSI  eth0
233:         10          0   IO-APIC-level  ehci_hcd:usb1
NMI:       5077       2032
LOC:  918809969  918809894
ERR:          0
MIS:          0

为了不让 CPU0 很累怎么把部分中断转移到 CPU1 上呢?或者说如何把 eth0 网卡的中断转到 CPU1 上呢?先查看一下 IRQ 90 中断的 smp affinity,看看当前中断是怎么分配在不同 CPU 上的(ffffffff 意味着分配在所有可用 CPU 上):

# cat /proc/irq/90/smp_affinity
7fffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff

在进一步动手之前我们需要先停掉 IRQ 自动调节的服务进程,这样才能手动绑定 IRQ 到不同 CPU,否则自己手动绑定做的更改将会被自动调节进程给覆盖掉。如果想修改 IRQ 90 的中断处理,绑定到第2个 CPU(CPU1):

# /etc/init.d/irqbalance stop
# echo "2" > /proc/irq/90/smp_affinity

过段时间在看 /proc/interrupts,是不是 90:eth0 在 CPU1 上的中断增加了(145)、在 CPU0 上的中断没变?不断打印 /proc/interrupts 就会发现 eth0 在 CPU0 上的中断数始终保持不变,而在 CPU1 上的中断数是持续增加的,这正是我们想要的结果:

# cat /proc/interrupts
CPU0       CPU1
0:  922506515          0    IO-APIC-edge  timer
1:          2          0    IO-APIC-edge  i8042
8:          0          0    IO-APIC-edge  rtc
9:          0          0   IO-APIC-level  acpi
12:          4          0    IO-APIC-edge  i8042
14:    8280147          0    IO-APIC-edge  ide0
50:        194          0   IO-APIC-level  ohci_hcd:usb2
58:      31907          0   IO-APIC-level  sata_nv
90:    1073399        145         PCI-MSI  eth0
233:         10          0   IO-APIC-level  ehci_hcd:usb1
NMI:       5093       2043
LOC:  922389696  922389621
ERR:          0
MIS:          0

有什么用

在网络非常 heavy 的情况下,对于文件服务器、高流量 Web 服务器这样的应用来说,把不同的网卡 IRQ 均衡绑定到不同的 CPU 上将会减轻某个 CPU 的负担,提高多个 CPU 整体处理中断的能力;对于数据库服务器这样的应用来说,把磁盘控制器绑到一个 CPU、把网卡绑定到另一个 CPU 将会提高数据库的响应时间、优化性能。合理的根据自己的生产环境和应用的特点来平衡 IRQ 中断有助于提高系统的整体吞吐能力和性能。

本人经常收到网友来信问到如何优化 Linux、优化 VPS、这个问题不太好回答,要记住的是性能优化是一个过程而不是结果,不是看了些文档改了改参数就叫优化了,后面还需要大量的测试、监测以及持续的观察和改进。

绑定进程到不同CPU

介绍了在 Linux 多核下如何绑定硬件中断到不同 CPU,其实也可以用类似的做法把进程手动分配到特定的 CPU 上,平时在 Linux 上运行的各种进程都是由 Linux 内核统一分配和管理的,由进程调度算法来决定哪个进程可以开始使用 CPU、哪个进程需要睡眠或等待、哪个进程运行在哪个 CPU 上等。如果你对操作系统的内核和进程调度程序感兴趣的话,不妨看看那本经典的 Operating Systems Design and Implementation(Linus Torvalds 就是看了这本书受到启发写出了 Linux),从简单的 Minix 入手,hack 内核是件很有意思的事情,本人以前修改过 Minix 内核的进程调度,学到了内核方面的很多东西。另外推荐一本课外读物:Just for Fun,Linus Torvalds 写的一本自传。

Linux 给我们提供了方便的工具用来手动分配进程到不同的 CPU 上(CPU Affinity),这样我们可以按照服务器和应用的特性来安排特定的进程到特定的 CPU 上,比如 Oracle 要消耗大量 CPU 和 I/O 资源,如果我们能分配 Oracle 进程到某个或多个 CPU 上并由这些 CPU 专门处理 Oracle 的话会毫无疑问的提高应用程序的响应和性能。还有一些特殊情况是必须绑定应用程序到某个 CPU 上的,比如某个软件的授权是单 CPU 的,如果想运行在多 CPU 机器上的话就必须限制这个软件到某一个 CPU 上。

安装 schedutils

在 CentOS/Fedora 下安装 schedutils:

# yum install schedutils

在 Debian/Ubuntu 下安装 schedutils:

# apt-get install schedutils

如果正在使用 CentOS/Fedora/Debian/Ubuntu 的最新版本的话,schedutils/util-linux 这个软件包可能已经装上了。

计算 CPU Affinity 和计算 SMP IRQ Affinity 差不多:

0x00000001    (CPU0)
0x00000002    (CPU1)
0x00000003    (CPU0+CPU1)
0x00000004    (CPU2)
...

使用 schedutils

如果想设置进程号(PID)为 12212 的进程到 CPU0 上的话:

# taskset 0x00000001 -p 12212

 

来自:http://os.51cto.com/art/201007/212964.htm

posted @ 2010-07-21 14:27 Jeremy1111 阅读(524) | 评论 (0)编辑 收藏

2010年7月20日

kmalloc

kmalloc 好像是总共只能使用 2M 内存的,如果用更多的就只能用 vmalloc,但其性能很糟糕;我所知道解决办法非常简单:启动系统的时候增加一个 mem=xxx 启动参数,让内核不去管理后面的那部分内存,然后在模块里面把后面的内存映射过来获得一大块连续的地址,以后根据自己的需要在那块空间上操作就可以了。
alloc是通过cache来实现的, 只不过每次kmalloc的大小不同, 因此是从不同的cache中分配:
/* include/linux/slab.h */
// 注意kmalloc是在头文件中定义的
static inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
// 以下是找一个对象大小刚好大于等于size的cache
int i = 0;
#define CACHE(x) \
if (size 
// 这是kmalloc_sizes.h文件内容, 实际就是定义CACHE中可用的对象大小
// 普通情况下最大是128K, 也就是kmalloc能分配的最大内存
#if (PAGE_SIZE == 4096)
CACHE(32)
#endif
CACHE(64)
#if L1_CACHE_BYTES512) || (MAX_NUMNODES > 256) || !defined(CONFIG_MMU)
CACHE(262144)
#endif
#ifndef CONFIG_MMU
CACHE(524288)
CACHE(1048576)
#ifdef CONFIG_LARGE_ALLOCS
CACHE(2097152)
CACHE(4194304)
CACHE(8388608)
CACHE(16777216)
CACHE(33554432)
#endif /* CONFIG_LARGE_ALLOCS */
#endif /* CONFIG_MMU
继续调用
void *__kmalloc(size_t size, gfp_t flags)
{
return __do_kmalloc(size, flags, NULL);
}

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
   void *caller)
{
struct kmem_cache *cachep;
/* If you want to save a few bytes .text space: replace
* __ with kmem_.
* Then kmalloc uses the uninlined functions instead of the inline
* functions.
*/
cachep = __find_general_cachep(size, flags);
if (unlikely(cachep == NULL))
return NULL;
return __cache_alloc(cachep, flags, caller);
}

static inline struct kmem_cache *__find_general_cachep(size_t size, gfp_t gfpflags)
{
struct cache_sizes *csizep = malloc_sizes;
#if DEBUG
/* This happens if someone tries to call
* kmem_cache_create(), or __kmalloc(), before
* the generic caches are initialized.
*/
BUG_ON(malloc_sizes[INDEX_AC].cs_cachep == NULL);
#endif
while (size > csizep->cs_size)
csizep++;
/*
* Really subtle: The last entry with cs->cs_size==ULONG_MAX
* has cs_{dma,}cachep==NULL. Thus no special case
* for large kmalloc calls required.
*/
if (unlikely(gfpflags & GFP_DMA))
return csizep->cs_dmacachep;
return csizep->cs_cachep;
}

__find_general_cachep确定可以分配的空间大小由malloc_sizes结构体数组指定,在阅读mm/slab.c中它的定义如下
struct cache_sizes malloc_sizes[] = {
#define CACHE(x) { .cs_size = (x) },
#include 
CACHE(ULONG_MAX)
#undef CACHE
};
在对一个结构体进行赋值的时候,第一次看到了这个用法,赶紧来仔细看看代码。
首先在slab_def.h中找到cache_sizes的定义如下:
struct cache_sizes { 
size_t cs_size; 
struct kmem_cache *cs_cachep; 
#ifdef CONFIG_ZONE_DMA 
struct kmem_cache *cs_dmacachep; 
#endif 
}; 
可以看出,无论cache_sizes的如何变,至少都要给cs_size分配一个值。函数一就是对malloc_sizes这个数组进行初始化。在初始化的时候却多了3行代码:
代码一:#define CACHE(x) { .cs_size = (x) }, 
作用:定义一个CACHE(x)的宏,作用在include里面。
代码二:#include
作用,引用kmalloc_size.h文件。使malloc_sizes[]得到初始化的值。换个角度看,把函数初始化和依据附加条件分开,使得程序的逻辑更加清晰。在函数中,CACHE仅仅作为一个宏,与具体的实现无关。
代码三:#undef CACHE 
作用:撤销代码一中的定义。

综合来讲,实现的功能就是:
#include 把kmalloc_sizes.h文件里面的内容原封不动地复制到数组中, 定义了一个宏CACHE(x),用完之后取消。
数组展开之后便成:
struct cache_sizes malloc_sizes[] = {
   { .cs_size = (32) },
   { .cs_size = (64) },
   { .cs_size = (96) },
   ...
  { .cs_size = (ULONG_MAX) }
};
在linux/kmalloc_sizes.h中可以看到,所有的代码做的事,仅仅是针对不同的硬件环境作出不同的反应。除了默认的分配CHACH(x)运算外,针对内存页面、一级缓存大小,和是否支持内存管理模块,是否允许kmalloc分配大于1MB的内存等,分别作出了不同的动作。
在面向对象的编程中,最基本的三个概念为:继承、封装、抽象。继承可以理解为在在当前已经存在的基础上进行革新;封装也就是说将一些内部的东西不给你看,你只需要知道如何操作;抽象的意思就是程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。oop对于类的编程一般是这个流程:首先是对当前对象进行初始化,接着针对对象进行一系列的操作,最后对当前对象进行销毁
再回到文首的三个代码运行流程,代码一首先定义一个宏;代码二接收到这个值,并且进行一系列的操作,是内部的,在slab.c中没有描述;代码三,销毁代码一定义的宏。同时,在mm/slab.c和include/linux/slab_def.h中可以多次看到上述三个代码,只是在每个代码的实现中,CACHE(x)定义为不同的宏。


用于kmalloc可分配的内存大小范围在32"131027(128k)字节,并且由于它用slab分配器来分配内存的,所以,得到的内存大小可能比你申请的要大一些(它向上取2的N次幂整数)。而且如果开启了CONFIG_LARGE_ALLOCS选项,这个值可以更大,可以达到了32M。


kmalloc(size)返回的是虚拟地址。因为该地址是经过映射后的地址。 
virt_addr = kmallolc( ... ) ; 
phys_addr = virt_to_phys( virt_addr ) ; 

参考一下virt_to_phys的实现我想就很明白了。

posted @ 2010-07-20 14:25 Jeremy1111 阅读(2018) | 评论 (1)编辑 收藏

2009年5月25日

CR&SR

Tseng   Lab   ET-4000  
  General(一般)寄存器组:  
          Miscellaneous   Output   Reg         写:0x3c2     读:0x3cc  
          Input   Status   Reg   0                                           读:0x3c3  
          Input   Status   Reg   1                                           读:0x3da  
          Feature   Control   Reg                   写:0x3da     读:0x3ca  
          Video   Subsystem   enable             写:0x3c3     读:0x3d3  
  Sequencer寄存器组:                           索引--读写:0x3c4  
                                                                  数据--读写:0x3c5  
  CRTC寄存器组:                                     索引--读写:0x3d4  
                                                                  数据--读写:0x3d5  
  Graphics寄存器组:                             索引--读写:0x3ce  
                                                                  数据--读写:0x3cf  
  Attribute寄存器组:                           索引--读写:0x3ce  
                                                                  数据--读:     0x3c0  
                                                                  数据--写:     0x3c1
///////////////////////////////////////////////////////////////////////////////////////////////////
VGA/TVGA是目前国内计算机上广泛使用的视频图形显示卡,特别
是TVGA显示卡,除了具备一般VGA的显示模式外,还提供了增强的图形
和文本显示模式,增强的图形显示提供了高分辨率图形显示手段。VGA/T
VGA都具备灵活方便的可编程图形控制特性,为实现诸如图像的淡变(淡进
、淡出)、滚动、动画等特技处理提供了良好的工作环境。本文介绍在TVG
A 9000和TVGA 8900上图形方式下的滚动技术及其实现方法。
图形滚动技术
图形滚动(简称滚动)是指将显示的图形平滑地水平、垂直、斜向移动。
滚动是计算机图形处理中不可缺少的重要技术之一。利用TVGA 9000
和TVGA 8900提供的下列一组寄存器,可实现各个方向的滚动:
1.起始地址寄存器
在VGA中,起始地址寄存器(SAR)16位长,用于对64K长的缓
冲区中的任一字节编址。屏幕可看作是观察位于显示缓冲区中的数据的窗口,
而窗口的大小由当前显示方式的分辨率决定,其最大值就是该系统的最高显示
分辨率。实际上,最大窗口的尺寸往往小于数据区(显示缓冲区)的尺寸。因
此要观察显示缓冲区中任意部分,就需要移动这个窗口。技术上即通过设置起
始地址寄存器实现。
如果起始地址改变1,就会使屏幕上显示的数据向左(加1)或向右(减
1)移动一个字节。在256色图形方式,就意味着水平方向上移动8个象素
(因为每个字节8个象素,每个象素占一位)。
如果起始地址改变每一行的水平字节数,就会使显示向上(减)或向下(
加)移动一行。如每行有80个字节,那么加80就会向下移动一行。
由于CRTC寄存器都是8位的,所以16位长的起始地址寄存器占了两
个寄存器,称为高位起始地址寄存器(索引号为0CH)和低位起始地址寄存
器(索引0DH),其读/写端口为03B5H(单显),03D5H(彩显
),寄存器地址选择口为03B4H(单显),03D4H(彩显)。寄存器
格式为:
7 0 7 0
-------- --------
SAH SHL
-------- --------
高位起始地址 低位起始地址
索引号 0CH 索引号 0DH
地址选择 03D4H/ 地址选择 03D4H/
03B4H 03B4H
读/写口 03D5H/ 读/写口 03D5H/
03B5H 03B5H
在TVGA中,由于它可工作于128K页方式,所以起始地址为17位
长,0-15位的配置方法与VGA相同,位16被放在CRTC模块测试寄
存器3X5.1EH的位5,这个寄存器的格式及相关位如下:
7 6 5 4 3 2 1 0
- - - - - - - -

16
- - - - - - - -
CRTC模块测试寄存器
索引号 01EH
地址选择 03D4H/03B4H
读/写口 03D5H/03B5H
2.属性地址寄存器
属性地址寄存器用于选择属性控制寄存器(位0-4)及调色板地址源(
位5),在滚动操作中,位5应该为1,即允许显示存储区访问调色板RAM
,禁止主机访问调色板(位5为0,允许主机访问调色板,禁止显示存储区访
问调色板)。该寄存器格式及相关位为:
7 6 5 4 3 2 1 0
- - - - - - - -
源 属性寄存器索引
- - - - - - - -
属性地址寄存器
写端口 03C0H
读端口 03C1H
3.水平象素侧移寄存器
当起始地址寄存器内容变更1时,在256色方式下,就会水平移动8个
象素,使滚动出现跳跃,为获得平滑的水平滚动效果,水平滚动必须一个象素
一个象素的移动,利用水平象素侧移寄存器即可实现这一目的。该寄存器的格
式及相关位为:
7 6 5 4 3 2 1 0
- - - - - - - -
水平移动象素数
- - - - - - - -
水平象素侧移寄存器(PPR)
索引号 013H
地址选择 03C0H
写端口 03C0H
读端口 03C1H
图形滚动技术的程序实现
下面是在配备TVGA 8900显示卡的8086系列机及其兼容机上
用汇编语言实现的256色图形的水平和垂直流动的程序,这些程序可以由C
语言程序直接引用。
1.向右水平滚动程序
在该程序中,BYTES_PER_LINE是指定的256色方式下的
每行字节数,如800X600方式时每行字节数为800/8=100。
因为每行字节编址从0到BYTES_PER_LINE-1,所以初始化起
始地址值为BYTES_PER_LINE-1,调整值为-1。每字节为8
位,编址从0到7,所以水平象素侧移寄存器的初值应为7,调整值为-1。
该程序的入口参数为要滚动的象素个数,程序自动控制为0到BYTES_P
ER_LINE之间。没有返回值。

posted @ 2009-05-25 16:23 Jeremy1111 阅读(396) | 评论 (0)编辑 收藏

如何使用windbg调试vista

在Vista中已经全面采用BootConfig的工具, 所以要设定 Kernel Debug必须遵照以下的步骤才能设定成功.

(1) 执行 Command Prompt 时, 以 Administrator 的身份. 因为只有 Administrator 身份才有权限改变 Boot Config, 其它的 Account 都不行 (即使是具有 Administrator 的权限也不行).

(2) 使用 bcdedit /dbgsettings 查看现在的设定值 ( Default 为 COM1, 115200 ), 可以更改为自行的设定, 命令为 bcdedit /dbgsettings 1394 channel:23 (例: 使用 1394, channel 为 23).
一般不需要修改。

(3) 复制一个开机选项, 命令为 bcdedit /copy {current} /d DebugEntry , DebugEntry为选项名称, 可以自行命名. 然后将结果的 GUID 值复制一份.

(4) 使用 bcdedit /debug {XXXXXXXX-XXXX-XXXX...(GUID)} ON 命令将此选项的 Debug 功能打开. 参数 /debug 后面接的 GUID 值为步骤 (3) 的结果.

(5) 改变开机选项的次序, 命令为 bcdedit /displayorder {current} {XXXXXXXX-XX....(GUID)}.

这一项其实一般不用执行。

(6) 使用 regedit 更改 registry 的项目. 在 HKLM"SYSTEM"CurrentControlSet"Control"Session Manager 的子机码下增加一个 Debug Print Filter 的 Key, 然后在这个 Key 之下增加一个 DEFAULT 的 ValueName, 并且设定此 Value 为 REG_DWORD 的型态, 其值为 0x00000008

这点比较重要,否则你在程序中使用DbgPrint输出的信息,在主机端将看不到。

(7) 重新开机, 就可以使用 Kernel Debug 了!!!!!

(转载)

posted @ 2009-05-25 16:18 Jeremy1111| 编辑 收藏

2008年7月29日

C语言字节对齐

今天在用C读一个BMP文件时,怎么也读不到正确的值,仔细跟踪了一下,发现原来是这个struct占了0x10个字节,本来它只占0x0e个字节,我一想这应该是个字节对齐的问题,所以找了一下,在这个struct的定义前面加一个宏定义:

#pragma pack(2)
typedef struct tagBITMAPFILEHEADER {
  WORD    bfType;   
  DWORD   bfSize;
  WORD    bfReserved1;
  WORD    bfReserved2;
  DWORD   bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
#pragma pack()

以上蓝色的是我加的,最后能够正常读出bmp文件头了。

以下是我找到的资料:

字节对齐1

一、快速理解

1. 什么是字节对齐?

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

2. 字节对齐有什么作用?

字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4 字节对齐的,GNU gcc 也是默认4字节对齐。

3. 更改C编译器的缺省字节对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

另外,还有如下的一种方式:
· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

4. 举例说明

例1

struct test
{
char x1;
short x2;
float x3;
char x4;
};

由于编译器默认情况下会对这个struct作自然边界(有人说“自然对界”我觉得边界更顺口)对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

例2

#pragma pack(1) //让编译器对这个结构作1字节对齐
struct test
{
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐

这时候sizeof(struct test)的值为8。

例3

#define GNUC_PACKED __attribute__((packed))
struct PACKED test
{
char x1;
short x2;
float x3;
char x4;
}GNUC_PACKED;

这时候sizeof(struct test)的值仍为8。

二、深入理解

什么是字节对齐,为什么要对齐?
TragicJun 发表于 2006-9-18 9:41:00

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
二.字节对齐对程序的影响:

    先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
    int a;
    char b;
    short c;
};
struct B
{
    char b;
    int a;
    short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)   
short:2(有符号无符号同)   
int:4(有符号无符号同)   
long:4(有符号无符号同)   
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?

    先让我们看四个重要的基本概念:


1.数据类型自身的对齐值:
  对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
    char b;
    int a;
    short c;
};
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

四.如何修改编译器的默认对齐值?

1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

五.针对字节对齐,我们在编程中如何考虑?
    如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
         struct A{
           char a;
           char reserved[3];//使用空间换时间
           int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

六.字节对齐可能带来的隐患:

    代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:

如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

**********************************
 

字节对齐2
  今天咱们就闲话少说直接进入正题了,(旁边来一人:靠!你不进入正题还想怎么样啊?你小子是不是找抽?)晕!这么想知道答案!别打偶,偶讲就是了......................................
让偶们先来看下面这个结构体:
struct  stu1
{
    int a;
    char b;
};
    来看看sizeof(stu)的结果为多少? (小A:  不就是5么~你小子还卖什么关子.......)
看结果是什么?没想到吧?呵呵!(得意洋洋!!)

(一屠夫上台: 怎么是8啊?你小子拿什么破编译器糊弄人)晕!屠夫也知道编译器??/

你先别急,再来看下一个例子:(急也没用!)

struct   stu2

{

  char b;

  int a;

}

这个sizeof(stu2)是多少?

(屠夫又来了:怎么还是8啊?)呵呵!拨开迷雾见晴天,想要知庐山真面目.现在DEGUG工具派上用场了。(小A:得八哥是什么东西?)
得八哥~~~~~~~DEGUG就是调试工具,它可以看看我的程序(欠扁啊?是我们的程序)在内存到底是怎么样的。

好了,现在创建一个结构体变量 stu2 s2 {" a ", 0x12345678h}; stu1 s1 {0x12345678, " a "}
运行DEGUG,怎么样发现了什么?

在第一个结构体中char b的后面内存有三个字节是添了数据的.也就是这样 78 56 34 12 61 cc cc cc  

而在第二个结构体中CHAR B的后面内存中也添加了数据.61 cc cc cc 78 56 34 12

这又是怎么回事呢?(问谁呢?你是干什么的?)

需要字节对齐当然有设计者的考虑了,原来这样有助于加快计算机的存取速度,否则就得多花指令周期了。所以,编译器通常都会对结构体进行处理,让宽度为2的基本数据类型(short等)

都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上。正是因为如此两个数中间就可能需要加入填充字节,所以结构体占的内存空间就增长了。


  其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
 
   1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

   2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。

    3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。例如上面第一个结构体变量。(哎呀!知道!真实多嘴!)

现在就可以解释上面的问题了,第一个结构体变量中成员变量最宽为4(SIZEOF(INT) = 4),所以S1变量首地址必须能被4整除。(不信你试试!)S1的大小也应该为4的整数倍。但是现在s1中

有 4 + 1 的空间,所以为了满足第三个条件就在char b的后面在加上三个字节的空间以凑够8个字节空间。第二个结构体变量S2中 成员变量最大宽度为4,而且按照以前的理解int a 的地址

和s2的地址相差5个字节,但是为了满足第而个条件(相差的距离------偏移地址必须是4的整数倍)所以在char b的后面添加了三个字节的空间以保证int a的偏移地址是4的整数倍即为4。

至于涉及到结构体嵌套的问题,你也可以用上述方法总结的,只不过你把被嵌套的结构体在原地展开就行了,不过在计算偏移地址的时候被嵌套的结构体是不能原地展开的必须当作整体。
嘿嘿!偶申明一点,上述三条建议不是偶说的,是做编译器的工程师总结出来的,偶只是借用而已。

呵呵!希望各给都明白了!
HAVE FUN!

字节对齐3

字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。因而对int、uint16、uint32等多于1字节类型的数据,在这些嵌入式平台上应该变换其存储顺序。通常我们认为,在空中传输的字节的顺序即网络字节序为标准顺序,考虑到与协议的一致以及与同类其它平台产品的互通,在程序中发数据包时,将主机字节序转换为网络字节序,收数据包处将网络字节序转换为主机字节序。

对于下面的结构体
struct test
{
     char x1;
     short x2;
     float x3;
     char x4;
};
结构各成员空间分配情况是怎样的?
文章中解释:
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在 test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了 3个空字节。整个结构所占据空间为12字节。
同时,说明了更改C编译器的缺省字节对齐方式: 在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
 · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
    · 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
     · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
     · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。


下面是CSDN上提出的问题(原文<<intel和微软和本公司同时出现的面试题>>:http://community.csdn.net/Expert/TopicView3.asp?id=3804035)
---------------------------------------
#pragma pack(8)

struct s1{
short a;
long b;
};

struct s2{
char c;
s1 d;
long long e;
};

#pragma pack()


1.sizeof(s2) = ?
2.s2的c后面空了几个字节接着是d?
---------------------------------------

下面是redleaves(ID最吊的网友)给出的解释:
很详尽,很透彻,从论坛原文后头的对话可以看出redleaves对C/C++的标准理解很深刻,值得偶学习:)
#pragma pack(8)
struct S1{
char a;
long b;
};
struct S2 {
char c;
struct S1 d;
long long e;
};
#pragma pack()
sizeof(S2)结果为24.
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.
S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.
a b
S1的内存布局:11**,1111,
c S1.a S1.b d
S2的内存布局:1***,11**,1111,****11111111

这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.

下面是从另外一篇文章(http://community.csdn.net/Expert/TopicView3.asp?id=3564500)的一些对各个类型以及结构体对齐方式的补充:

为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4整除。变量的对齐规则如下(32位系统):
Type
Alignment
char
在字节边界上对齐
short (16-bit)
在双字节边界上对齐
int and long (32-bit)
在4字节边界上对齐
float
在4字节边界上对齐
double
在8字节边界上对齐
structures
单独考虑结构体的个成员,它们在不同的字节边界上对齐。
其中最大的字节边界数就是该结构的字节边界数。

如果结构体中有结构体成员,那么这是一个递归的过程。
设编译器设定的最大对齐字节边界数为n,对于结构体中的某一成员item,它相对于结构首地址的实际字节对齐数

目X应该满足以下规则:
X = min(n, sizeof(item))
例如,对于结构体
struct {
char a;
long b;
} T;
当位于32位系统,n=8时:
a的偏移为0,
b的偏移为4,中间填充了3个字节, b的X为4;

当位于32位系统,n=2时:
a的偏移为0,
b的偏移为2,中间填充了1个字节,b的X为2;
结构体的sizeof:
设结构体的最后一个成员为LastItem,其相对于结构体首地址的偏移为offset(LastItem),其大小为sizeof(LastItem),结构体的字节对齐数为N,则:结构体的sizeof 为: 若offset(LastItem)+ sizeof(LastItem)能够被N整除,那么就是offset(LastItem)+ sizeof(LastItem),否则,在后面填充,直到能够被N整除。
另外:
1) 对于空结构体,sizeof == 1;因为必须保证结构体的每一个实例在内存中都有独一无二的地址.
2)结构体的静态成员不对结构体的大小产生影响,因为静态变量的存储位置与结构体的实例地址无关。例如:
struct {static int I;} T; struct {char a; static int I;} T1;
sizeof(T) == 1; sizeof(T1) == 1;
 
http://blog.chinaunix.net/u1/55630/showart_491497.html

posted @ 2008-07-29 18:14 Jeremy1111 阅读(499) | 评论 (0)编辑 收藏

2008年7月24日

C++ Template 中的typename

typename另外一种用法说明
typedef typename      _Container::iterator                       __iter;
               这是个类 类里某种类型

在C++标准过程中,引入关键字typename是为了说明:模板内部的标识符可以是一个类型.

posted @ 2008-07-24 16:40 Jeremy1111 阅读(265) | 评论 (0)编辑 收藏

2008年7月23日

模板

函数模板的一般定义形式是:
template<类型形式参数表>返回类型FunctionName(形式参数表)
{
//函数体定义
}
这样的函数模板定义不是一个实实在在的函数,编译系统不为其产生任何执行代码.

函数模板与模板函数的区别
函数模板是模板的定义,定义中用到通类型参数.
模板函数是实实在在的函数定义,它由编译系统在碰见具体的函数调用时所生成,具有程序代码.
template<class T>
T max(T a, T b)
{
return a>b?a:b;
}
可以像重载普通函数那样重载模板函数.
char* max(char* a, char* b)
{
  return (strcmp(a,b)?a:b);
}


类模板的一般说明形式:
template<类型形式参数表>class className
{
//类声明体
};
template<类型形式参数表>返回类型className<类型名表>::MemberFuncName1(形式参数表)
{
//成员函数定义体
}
其中的类型形式参数表与函数模板中的意义一样.后面的成员函数定义中,class_name<类型名表>中的类型名表,是类型形式参数的使用.
这样的一个说明(包括成员函数定义),不是一个实实在在的类,只是对类的描述,称为类模板(class template)
建立类模板之后,可用下列方式创建类模板的实例:
className<类型实在参数表>object;
其中类型实在参数表应与该类模板中的类型形式参数表匹配.class_name<类型实在参数表>是模板类(template class),object是该模板类的一个对象.

类模板与模板类的区别
类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数.
模板类是实实在在的类定义,是类模板的实例化.类定义中参数被实际类型所代替.
template<class T>class List
{
public:
List();
....
}

template<类型形式参数表>返回类型className<类型名表>::MemberFuncName1(形式参数表)
template<class T>                                    List            <T>         ::List()
{
...
}

posted @ 2008-07-23 15:43 Jeremy1111 阅读(192) | 评论 (0)编辑 收藏

2008年7月21日

调试

好的网址:http://advdbg.org/blogs/advdbg_system/articles/category/1033.aspx

posted @ 2008-07-21 13:49 Jeremy1111 阅读(166) | 评论 (0)编辑 收藏