ntu*_*ntu 42 c string gcc compilation hex-editors
当我使用不同的编译器编译此代码并检查十六进制编辑器中的输出时,我希望在某处找到字符串“Nancy”。
#include <stdio.h>
int main()
{
char temp[6] = "Nancy";
printf("%s", temp);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
的输出文件gcc -o main main.c如下所示:
的输出g++ -o main main.c,我无法在任何地方找到“Nancy”。
在 Visual Studio (MSVC 1929) 中编译相同的代码,我在十六进制编辑器中看到完整的字符串:
为什么(1)中的字符串中间会出现一些随机字节?
Eri*_*hil 37
关于编译器如何在其生成的输出文件中存储数据并没有单一的规则。
\n数据可以存储在 \xe2\x80\x9cconstant\xe2\x80\x9d 部分中。
\n数据可以内置到指令的 \xe2\x80\x9cimmediate\xe2\x80\x9d 操作数中,其中数据被编码在编码指令的位的各个字段中。
\n可以通过编译器生成的指令从其他数据计算数据。
\n我怀疑您在一个地方看到 \xe2\x80\x9cNanc\xe2\x80\x9d 而在另一个地方看到 \xe2\x80\x9cy\xe2\x80\x9d 的情况是使用加载指令的编译器(可以用 \ 编写) xe2\x80\x9cmov\xe2\x80\x9d) 加载形成 \xe2\x80\x9cNanc\xe2\x80\x9d 的字节作为立即操作数,另一个加载指令加载形成 \xe2\x80\x9cy\xe2 的字节\x80\x9d 带有尾随空字符,以及其他指令,用于将加载的数据存储在堆栈上并将其地址传递给printf.
您没有提供足够的信息来诊断此g++情况:您没有命名编译器或其版本号,也没有提供生成的输出的任何部分。
The*_*zer 18
我在 x86-64 系统(Intel
的结果hexdump -C:
请注意,字节序列是相同的。
所以我用gcc -S -c:
.file "teststr.c"
.text
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movl $1668178254, -14(%rbp) # NOTE THIS PART HERE
movw $121, -10(%rbp) # AND HERE
leaq -14(%rbp), %rax
movq %rax, %rsi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L3
call __stack_chk_fail@PLT
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
Run Code Online (Sandbox Code Playgroud)
突出显示的值1668178254是636E614EASCII 编码中的十六进制或“cnaN”(由于 x86 是小端系统,因此字节顺序反转,变为“Nanc”),并且121是十六进制79或“y”。
因此,它使用两个移动指令,而不是从文件的字节字符串部分循环复制,因为它是一个短字符串,并且中间的“垃圾”是(我相信)以下movw指令。可能是一种优化初始化的方法,而不是通过内存逐字节循环,即使没有“正式”给编译器提供优化标志 - 就是这样,编译器可以在这方面做它想做的事情。那么,微软的编译器在编译方式上似乎更加“迂腐”,因为事实上,它显然放弃了这种优化,转而将字符串连续地放在一起。
通常,编译后的程序被分成不同类型的“部分”。汇编程序文件将使用指令在它们之间进行切换。
C 中的字符串文字可以以两种不同的方式使用。
如果字符串文字用作指针,则编译器可能会将字符串数据放置在只读数据部分中。
如果使用字符串文字来初始化全局/静态数组,则编译器很可能会将数组放置在初始化数据部分中(如果数组声明为 const,则将其放置在只读数据部分中)。
但是,在您的情况下,您正在初始化的数组是一个自动局部变量。因此不能在程序启动前进行预初始化。编译器必须包含代码以在每次函数运行时对其进行初始化。
编译器可能会选择将字符串存储在只读数据位置,然后使用复制例程(内联或调用)将其复制到本地数组。它可能选择简单地生成指令来一一设置数组的元素。它可以选择生成同时设置多个数组元素的指令。
在您的示例中,MSVC 似乎已选择使用复制例程,因此该字符串按顺序出现在文件中。另一方面,gcc 选择使用 4 字节移动指令,后跟两字节移动指令,两者都以文字作为输入。所以文字被分成两部分。
PS 我注意到有些人在这个问题的其他答案上发布了 https://godbolt.org/ 链接。编译器资源管理器是一个有用的工具,但请注意,它默认隐藏汇编器输出中的节切换指令。