以编译的二进制显示字符串

Mar*_*ter 15 c string gcc

让我们假设我有简单的C程序,我编译它gcc -o hello hello.c:

#include<stdio.h>

main()
{
    printf("hello");
}
Run Code Online (Sandbox Code Playgroud)

现在我想用strings实用程序显示"字符串" :

$ strings hello
/lib64/ld-linux-x86-64.so.2
__gmon_start__
libc.so.6
printf
__libc_start_main
GLIBC_2.2.5
fffff.
l$ L
t$(L
|$0H
hello
;*3$"
Run Code Online (Sandbox Code Playgroud)

并且,正如预期的那样,我可以在二进制文件中看到字符串"hello".

但是,当我修改我的C程序并将"hello"作为常量时:

#include<stdio.h>

char s[6] = {'h','e','l','l','o','\0' } ;

main()
{
    printf("%s\n", s);
}
Run Code Online (Sandbox Code Playgroud)

我再也看不到二进制文件中的字符串"hello"了.

有人可以解释一下原因吗?

Grz*_*ski 5

man 1 strings(强调我的):

对于给定的每个文件,GNU字符串打印至少4个字符长的可打印字符序列(或使用下面的选项给出的数字),后跟一个不可打印的字符. 默认情况下,它只打印来自目标文件的初始化和加载部分的字符串 ; 对于其他类型的文件,它会打印整个文件中的字符串.

C语言没有将字符串定义为一等公民.它们表示字符串数组字符串文字.例如,在这样的基本程序中:

#include <stdio.h>

int main(void)
{
    char s[] = "my string";

    printf("%s\n", s);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我们可以合理地说s数组包含一个字符串.请注意,这个是在堆栈上分配的.它具有自动存储持续时间,与您的问题中的示例相反,其中s明确定义了main(和任何)功能.

现在,回到你的问题,两个程序中的两个底层对象共享相同的特征:

  • 它们属于类型char[6]并具有相同的内容(C11§6.2.5/ p20),
  • 它们具有静态存储持续时间,这意味着它们必须在程序执行之前从概念上进行初始化(C11§5.1.2/ p1).

唯一的区别是,对字符串文字进行调整会调用未定义的行为,因此编译器可能会选择将它们放入单独的(例如只读)内存位置.

C11§6.2.5/ p20 类型:

数组类型描述了具有特定成员对象类型的连续分配的非空对象集,称为元素类型.

C11§5.1.2/ p1 执行环境:

具有静态存储持续时间的所有对象在程序启动之前初始化(设置为其初始值).

从更实用的角度来看,除了该strings命令之外,您还可以使用gdb调试器分析程序,更具体地说,使用x/s命令.这是基本的例子:

$ gcc -g hello.c -o hello
$ gdb -q hello
Reading symbols from /home/grzegorz/hello...done.
(gdb) disas /m main
Dump of assembler code for function main:
6   {
   0x00000000004004c4 <+0>: push   %rbp
   0x00000000004004c5 <+1>: mov    %rsp,%rbp

7       printf("%s\n", s);
   0x00000000004004c8 <+4>: mov    $0x60086c,%edi
   0x00000000004004cd <+9>: callq  0x4003b8 <puts@plt>

8   }
   0x00000000004004d2 <+14>:    leaveq 
   0x00000000004004d3 <+15>:    retq   

End of assembler dump.
(gdb) x/s 0x60086c
0x60086c <s>:    "hello"
Run Code Online (Sandbox Code Playgroud)

您可能希望比较disas程序的命令结果,并查看它们之间是否存在一些差异.