- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我写了一个C程序,只读取/写入一个大数组。出于好奇,我使用命令gcc -O0 program.c -o program
编译了该程序,然后使用objdump -S
命令反编译了C程序。
该问题末尾附带了read_array
和write_array
函数的代码和汇编。
我试图解释gcc如何编译函数。我使用//
添加了我的评论和问题
取一首write_array()
函数的汇编代码的开头
4008c1: 48 89 7d e8 mov %rdi,-0x18(%rbp) // this is the first parameter of the fuction
4008c5: 48 89 75 e0 mov %rsi,-0x20(%rbp) // this is the second parameter of the fuction
4008c9: c6 45 ff 01 movb $0x1,-0x1(%rbp) // comparing with the source code, I think this is the `char tmp` variable
4008cd: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp) // this should be the `int i` variable.
char tmp
显然是在
int i
函数中的
write_array
之后定义的。为什么gcc对这两个局部变量的存储位置重新排序?
int i
在
-0x8(%rbp)
处,
char tmp
在
-0x1(%rbp)
处,这表示变量
int i
占用7个字节?这很奇怪,因为
int i
在x86-64机器上应该是4个字节。是不是我的猜测是gcc试图做一些调整?
#define CACHE_LINE_SIZE 64
static inline void
read_array(char* array, long size)
{
int i;
char tmp;
for ( i = 0; i < size; i+= CACHE_LINE_SIZE )
{
tmp = array[i];
}
return;
}
static inline void
write_array(char* array, long size)
{
int i;
char tmp = 1;
for ( i = 0; i < size; i+= CACHE_LINE_SIZE )
{
array[i] = tmp;
}
return;
}
write_array
的反汇编代码:
00000000004008bd <write_array>:
4008bd: 55 push %rbp
4008be: 48 89 e5 mov %rsp,%rbp
4008c1: 48 89 7d e8 mov %rdi,-0x18(%rbp)
4008c5: 48 89 75 e0 mov %rsi,-0x20(%rbp)
4008c9: c6 45 ff 01 movb $0x1,-0x1(%rbp)
4008cd: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
4008d4: eb 13 jmp 4008e9 <write_array+0x2c>
4008d6: 8b 45 f8 mov -0x8(%rbp),%eax
4008d9: 48 98 cltq
4008db: 48 03 45 e8 add -0x18(%rbp),%rax
4008df: 0f b6 55 ff movzbl -0x1(%rbp),%edx
4008e3: 88 10 mov %dl,(%rax)
4008e5: 83 45 f8 40 addl $0x40,-0x8(%rbp)
4008e9: 8b 45 f8 mov -0x8(%rbp),%eax
4008ec: 48 98 cltq
4008ee: 48 3b 45 e0 cmp -0x20(%rbp),%rax
4008f2: 7c e2 jl 4008d6 <write_array+0x19>
4008f4: 5d pop %rbp
4008f5: c3 retq
最佳答案
即使在-O0
,除非有调用方,否则gcc不会为static inline
函数提供定义。在这种情况下,它实际上不是内联的:而是发出独立的定义。因此,我想您的拆卸就是出于此。
您使用的是旧版gcc吗? gcc 4.6.4将vars按该顺序放在堆栈上,但是4.7.3及更高版本使用其他顺序:
movb $1, -5(%rbp) #, tmp
movl $0, -4(%rbp) #, i
在您的asm中,它们是按初始化顺序而不是声明顺序存储的,但是我认为这只是偶然,因为顺序在gcc 4.7中已更改。另外,像
int i=1;
这样的初始化程序不会改变分配顺序,因此完全破坏了该理论。
-O0
doesn't mean "no optimization"。您应该认为
-O0
忽略了
-O3
通常会做的一些事情。没有选择尝试从源代码到asm进行尽可能原义的转换。
char
的rbp-1
:这是第一个可以保存char
的位置。如果还有另一个char
需要存储,则可以放在rbp-2
处。int
的rbp-8
:由于从rbp-1
到rbp-4
的4个字节不是免费的,因此下一个可用的自然对齐位置是rbp-8
。%rsp-5
,而不是
%rsp-8
,但这不会节省任何内容。
%rsp+8
(堆栈args的开始)在输入之前对齐函数入口。)
%rbp-8
触摸
%rbp-5
不会进入的新页面或缓存行的唯一方法是使堆栈小于4B对齐。即使在32位代码中,这也是极不可能的。
%rsp
(That size was chosen because a one-byte displacement can go up to -128
)下方的128B红色区域。信号处理程序和用户空间堆栈的任何其他异步用户都将避免破坏红色区域,这就是为什么函数可以在不降低
%rsp
的情况下写入
%rsp
以下的内存的原因。因此,从这个 Angular 来看,我们使用多少红色区域无关紧要;信号处理程序耗尽堆栈的机会不受影响。
sub $16, %esp
在堆栈上保留空间。 (尝试在Godbolt上使用
-m32
)。同样,使用5个字节还是8个字节都没有关系,因为我们以16为单位进行保留。
char
和
int
变量很多时,即使将声明混合在一起,gcc也会将
char
打包到4B组中,而不是浪费空间来分散碎片:
void many_vars(void) {
char tmp = 1; int i=1;
char t2 = 2; int i2 = 2;
char t3 = 3; int i3 = 3;
char t4 = 4;
}
with gcc 4.6.4 -O0 -fverbose-asm
,有助于标记哪个存储是哪个变量,这就是为什么编译器asm输出比反汇编更可取的原因:
pushq %rbp #
movq %rsp, %rbp #,
movb $1, -4(%rbp) #, tmp
movl $1, -16(%rbp) #, i
movb $2, -3(%rbp) #, t2
movl $2, -12(%rbp) #, i2
movb $3, -2(%rbp) #, t3
movl $3, -8(%rbp) #, i3
movb $4, -1(%rbp) #, t4
popq %rbp #
ret
我认为变量根据cct版本在
-O0
处以声明的正向或反向顺序进行。
read_array
函数的一个版本,该版本可在以下方面进行优化:
// assumes that size is non-zero. Use a while() instead of do{}while() if you want extra code to check for that case.
void read_array_good(const char* array, size_t size) {
const volatile char *vp = array;
do {
(void) *vp; // this counts as accessing the volatile memory, with gcc/clang at least
vp += CACHE_LINE_SIZE/sizeof(vp[0]);
} while (vp < array+size);
}
Compiles to the following, with gcc 5.3 -O3 -march=haswell:
addq %rdi, %rsi # array, D.2434
.L11:
movzbl (%rdi), %eax # MEM[(const char *)array_1], D.2433
addq $64, %rdi #, array
cmpq %rsi, %rdi # D.2434, array
jb .L11 #,
ret
将表达式强制转换为void是告诉编译器已使用值的规范方法。例如要禁止使用未使用的变量警告,可以编写
(void)my_unused_var;
。
volatile
指针取消引用确实可以生成内存访问,而无需tmp变量。 C标准对于构成对
volatile
的访问的构成是非常非特定的,因此这可能不是完全可移植的。另一种方法是将读取的值
xor
到累加器中,然后将其存储到全局值中。只要您不使用整个程序优化,编译器就不会知道没有东西读取全局,因此它无法优化计算。
vmtouch
source code。 (它实际上为累加器使用了一个全局变量,这使代码变得笨拙。当然,这几乎无关紧要,因为它接触的是页面,而不仅仅是高速缓存行,因此即使在内存读取的情况下,TLB丢失和页面错误的瓶颈也会很快出现,在循环执行的依赖链中进行修改-写入。)
size
最初非零)时失败了。 GCC始终希望针对
add rsi,rdi
循环条件使用
cmp/jcc
,即使使用
-march=haswell
,
sub rsi,64
/
jae
可以像
cmp/jcc
一样进行宏融合。但一般而言,在AMD上,GCC的内部循环较少。
read_array_handtuned_haswell:
.L0
movzx eax, byte [rdi] ; overwrite the full RAX to avoid any partial-register false deps from writing AL
add rdi, 64
sub rsi, 64
jae .L0 ; or ja, depending on what semantics you want
ret
Godbolt Compiler Explorer link with all my attempts and trial versions
je
,我可以得到类似
do { ... } while( size -= CL_SIZE );
这样的循环,但是我似乎无法说服gcc在减去时捕获无符号借位。它要先减去然后再用
cmp -64/jb
来检测下溢。这是
not that hard to get compilers to check the carry flag after an add to detect carry:/
关于c - 为什么gcc对函数中的局部变量重新排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36298567/
这个问题在这里已经有了答案: How does Scala's apply() method magic work? (3 个回答) 9年前关闭。 假设我在 scala 中有一个 MyList 类,其
这个问题在这里已经有了答案: What is a non-capturing group in regular expressions? (18 个回答) Reference - What does
这个问题是针对嵌入式系统的! 我有以下选项来初始化一个对象: Object* o = new Object(arg); 这会将对象放入堆中并返回指向它的指针。我不喜欢在嵌入式软件中使用动态分配。 Ob
我自己搜索过,没能成功的正则表达式。 我有一个 html 文件,其中包含 [] 之间的变量我想把每一个字都写进去。 [client_name][client_company] [cl
我是 Python 新手。我不明白为什么这段代码不起作用: reOptions = re.search( "[\s+@twitter\s+(?P\w+):(?P.*?)\s+]", d
在过去 7 个月左右的时间里,我几乎一直在使用 .NET C# 进行编程。在那之前,我的大部分编程都是用 C++(从学校里学的)。在工作中,我可能需要在接下来的几个月里做一大堆 C 语言。我对 C 的
我是 RE 的新手,我正在尝试获取歌词并分离出歌词标题、和声和主唱: 下面是一些歌词的例子: [Intro] D.A. got that dope! [Chorus: Travis Scott] Ic
这可能是不可能的,但我想检查是否可以用一种简单的方式表达这样的事情: // obviously doesn't work class Foo : IFoo where T: Bar {
我们的应用程序中有“user”和“study”实体,存储在它们各自的表中。一项研究代表一种研究和已收集的数据。它们是多对多的关系,所以我们需要一个链接表:studies_users。 我们为用户分配角
将测试条件添加到 Visual Studio 2010 数据库单元测试(对于 SQL Server 2008)时,这些条件称为例如rowCountCondition1、rowCountConditio
在模拟器上,我可以从设置中卸载 SD 卡。 然后我可以将它安装到我的操作系统上,然后正常卸载它。 我一直无法弄清楚如何在模拟器上重新安装它(无需重新启动)。 提示: adb 命令 remount 是无
假设在一个分支上执行了一系列提交,但该分支尚未与主干重新同步。是否可以从提交中生成全局补丁?是否可以从一系列提交中生成“分组”补丁?如果是,如何? 最佳答案 svn diff -rXXX:YYY UR
在某些情况下,我想在我的应用程序中锁定调整大小功能,为此我尝试对属性进行数据绑定(bind),并且不允许在某些情况下更改它,但没有成功。 有没有办法这样做? 这是我不成功的尝试: XAML: Vie
当我的计算机连接多个显示器时,我可以检测它们,并根据从获取的值设置位置来向它们绘制图形 get(0, 'MonitorPositions') 但是,当我在 MATLAB 运行时断开监视器时,此属性不会
我们有一个grails应用程序,该应用程序在grails数据库中存储了各种域对象。该应用程序连接到第二个数据库,运行一些原始sql,并在表中显示结果。它基本上是一个报告服务器。 我们通过在DataSo
无法比较来自不同容器的迭代器(参见这里的示例: https://stackoverflow.com/a/4664519/225186 )(或者从技术上讲,它不需要有意义。) 这就提出了另一个问题,来自
我有以下情况: 家长 Activity : ParentActivityClass { private Intent intent; @Override public void onCreate(Bu
我经常将元素与附加功能 Hook ,例如: $('.myfav').autocomplete(); $('.myfav').datepicker(); $('.myfav').click(somefu
因此,我将 tooltipster.js 库用于工具提示,并尝试更改工具提示在不同屏幕尺寸上的默认距离。 所以这是默认的 init 的样子: $(inputTooltipTrigger).tool
我在 ARM7 嵌入式环境中工作。我使用的编译器不支持完整的 C++ 功能。它不支持的一项功能是动态类型转换。 有没有办法实现dynamic_cast<>() ? 我使用 Google 寻找代码,但到
我是一名优秀的程序员,十分优秀!