2016-11-18 51 views
62

我注意到字符串文字在内存中的地址与其他常量和变量(Linux操作系统)非常不同:它们有许多前导零(未打印)。在Linux上,为什么字符串文字的内存地址与其他字符串的内存地址不同?

实施例:

const char *h = "Hi"; 
int i = 1; 
printf ("%p\n", (void *) h); 
printf ("%p\n", (void *) &i); 

输出:

0x400634 
0x7fffc1ef1a4c 

我知道它们被存储在可执行文件的.rodata一部分。操作系统是否有特殊的方式处理它,所以文字最终会存储在一个特殊的内存区域(前导零)?该内存位置是否有任何优势,或者是否有其他特殊之处?

+0

http://stackoverflow.com/questions/4560720/why-does-the-stack-address-grow-towards-decreasing-memory-addresses –

+5

这完全取决于它加载代码的操作系统以及它的位置分配堆栈。 –

+8

显然是实现指定的,但RO数据(您的文字)通常会加载到标记为保护模式写入异常触发的单独页面中。含义:写给它会产生一个结构化的例外。 – WhozCraig

回答

73

下面是如何处理内存(从http://www.thegeekstuff.com/2012/03/linux-processes-memory-layout/)奠定了在Linux上:

Linux process memory layout

.RODATA部分是初始化的全局数据块的写保护小节。 (其中ELF可执行文件指定。数据段是用于初始化为非零值写入全局的可写副本。初始化为零可写全局转到的.bss块。通过全局这里我指的全局变量和所有静态变量,不管放置位置如何。)

图片应解释您的地址的数值。

如果你想进一步调查,然后在Linux上,你可以检查 的/ proc/$ PID /映射虚拟其描述正在运行的进程的内存布局文件。您不会得到保留(以点开头)ELF节名称,但您可以通过查看其内存保护标志来猜测内存块的起源于哪个ELF节。例如,运行

$ cat /proc/self/maps #cat's memory map 

给我

00400000-0040b000 r-xp 00000000 fc:00 395465        /bin/cat 
0060a000-0060b000 r--p 0000a000 fc:00 395465        /bin/cat 
0060b000-0060d000 rw-p 0000b000 fc:00 395465        /bin/cat 
006e3000-00704000 rw-p 00000000 00:00 0         [heap] 
3000000000-3000023000 r-xp 00000000 fc:00 3026487      /lib/x86_64-linux-gnu/ld-2.19.so 
3000222000-3000223000 r--p 00022000 fc:00 3026487      /lib/x86_64-linux-gnu/ld-2.19.so 
3000223000-3000224000 rw-p 00023000 fc:00 3026487      /lib/x86_64-linux-gnu/ld-2.19.so 
3000224000-3000225000 rw-p 00000000 00:00 0 
3000400000-30005ba000 r-xp 00000000 fc:00 3026488      /lib/x86_64-linux-gnu/libc-2.19.so 
30005ba000-30007ba000 ---p 001ba000 fc:00 3026488      /lib/x86_64-linux-gnu/libc-2.19.so 
30007ba000-30007be000 r--p 001ba000 fc:00 3026488      /lib/x86_64-linux-gnu/libc-2.19.so 
30007be000-30007c0000 rw-p 001be000 fc:00 3026488      /lib/x86_64-linux-gnu/libc-2.19.so 
30007c0000-30007c5000 rw-p 00000000 00:00 0 
7f49eda93000-7f49edd79000 r--p 00000000 fc:00 2104890     /usr/lib/locale/locale-archive 
7f49edd79000-7f49edd7c000 rw-p 00000000 00:00 0 
7f49edda7000-7f49edda9000 rw-p 00000000 00:00 0 
7ffdae393000-7ffdae3b5000 rw-p 00000000 00:00 0       [stack] 
7ffdae3e6000-7ffdae3e8000 r--p 00000000 00:00 0       [vvar] 
7ffdae3e8000-7ffdae3ea000 r-xp 00000000 00:00 0       [vdso] 
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0     [vsyscall] 

第一r-xp块肯定从来的.text(可执行代码), 第一r--p块从.RODATA,下面rw--来自.bss的块。数据。 (在堆和堆栈块之间是由动态链接器从动态链接库加载的块。)


注:要符合标准,你应该投的int*"%p"(void*)否则行为是不确定的。

+0

谢谢,这是有用的!但是如果我有多个进程,这仍然会发生。所以它并不是一个接一个地展开,而是从多个进程中获取所有“初始化全局数据”并将其存储在一起? – Noidea

+7

@Noidea不同的进程有不同的地址空间。一个进程中的0xDEADBEEF(通常)与另一个进程中的0xDEADBEEF完全无关。上述与调试和块增长相关的布局有一些明显的小优点(特别是对于堆块,尽管如果不能再使用mmap对堆进行碎片化并不是什么大问题)。另外,出于安全原因,实际映射的地址通常会有些随意。 – PSkocik

+6

@Noidea:不要将物理地址(对应于RAM中的地址)与虚拟内存地址(进程中的地址)混淆。 [内存管理单元](https://en.wikipedia.org/wiki/Memory_management_unit)的作用是将虚拟转换为物理,进程使用的所有地址都通过MMU进行转换。每个进程都有自己的MMU表,由OS管理。 –

0
printf ("%p\n", h); // h is the address of "Hi", which is in the rodata or other segments of the application. 
printf ("%p\n", &i); // I think "i" is not a global variable, so &i is in the stack of main. The stack address is by convention in the top area of the memory space of the process. 
+1

这似乎并不能回答被问到的问题。作为提醒,问题是“操作系统是否专门处理它?这种处理方式有什么优点吗?”你的回答似乎没有解决这些问题。你想编辑你的答案以更直接地解决问题吗? –

16

这是因为字符串文字具有静态存储时间。也就是说,他们将在整个项目中生活。这样的变量可以存储在既不在所谓的堆也不在堆栈上的特殊存储器位置中。因此地址的差异。

7

请记住,如果指针与指针指向的位置不同。一个更为现实的(苹果对苹果)比较是

printf ("%p\n", (void *) &h); 
printf ("%p\n", (void *) &i); 

我怀疑你会发现,hp有类似的地址。或者,另一个更现实的比较是

static int si = 123; 
int *ip = &si; 
printf ("%p\n", (void *) h); 
printf ("%p\n", (void *) ip); 

我怀疑你会发现,hip点的记忆类似区域。

+3

不,“h”已经是char指针了,所以'&h没有任何用处。写'h'和'&i'是正确的,因为它们分别是被引用字符串和'int'的地址。 –

+1

@underscore_d我想你完全误解了我的问题和答案。没有什么“正确的”或“不正确的”关于编写'h'和'OP仅仅是疑惑为什么他的系统上的实际地址是如此不同。我的观点是,如果你写'&h'和'&i',或者'h'和'ip',你很可能会看到更多类似的地址,这个练习将会(希望)帮助你理解为什么'h'和'&我'是如此不同。 –

+3

@SteveSummit指向字符串文字的指针将是另一个堆栈变量。但我想知道为什么字符串文字的地址与堆栈变量的地址差别很大。不是为什么两个堆栈变量的地址相似;) – Noidea

1

考虑文字是只读变量,并且还有文字池的概念。文字池是程序唯一文字的集合,其中重复的常量被丢弃,因为引用被合并为一个。

每个源都有一个文字池,根据链接/绑定程序的复杂程度,文字池可以彼此相邻放置以创建一个.rodata。

也不保证文字池是只读保护的。尽管编译器设计的语言如此。

考虑我的代码片段。我可以有

const char * cp =“hello world”;
const char * cp1 =“hello world”;

好编译器会认识到该源代码中,只读字面CP,CP1,都指向相同的字符串,并将使CP1点CP的文字,丢弃第二个。

还有一点。文字池可能是256bytes或不同值的倍数。如果池数据少于256个字节,松弛将用十六进制零填充。

不同的编译器,按照共同发展的标准,允许与Ç编译的模块,以与汇编语言或其他语言编写的模块连接。两个文字池连续放置在.rodata中。

相关问题