为什么代码中的小改动不会影响 exe 文件的大小?
Why don't small changes in code affect exe file size?
我很好奇 - 有时我会更改我的代码,重新编译,然后将我的 exe 或 dll 文件复制到旧版本上,然后看到 Windows 告诉我文件的日期已更改,但是大小保持完全相同。这是为什么?
例如,我使用以下控制台应用程序进行了测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = 2;
Console.WriteLine(a + b);
}
}
}
这生成了一个 5120 字节的 exe 文件(Visual Studio 2012,调试版本)。然后,我将代码更改为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = 2;
int c = 3;
Console.WriteLine(a + b + c);
}
}
}
exe大小完全一样
我看了一下反汇编,它显示了 IL 代码中的差异,所以差异不可能被优化掉:
第一个版本:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 2
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldc.i4.2
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldloc.1
IL_0007: add
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
IL_000e: ret
} // end of method Program::Main
第二个版本:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 a,
[1] int32 b,
[2] int32 c)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldc.i4.2
IL_0004: stloc.1
IL_0005: ldc.i4.3
IL_0006: stloc.2
IL_0007: ldloc.0
IL_0008: ldloc.1
IL_0009: add
IL_000a: ldloc.2
IL_000b: add
IL_000c: call void [mscorlib]System.Console::WriteLine(int32)
IL_0011: nop
IL_0012: ret
} // end of method Program::Main
如果代码体积更大,文件大小怎么可能完全一样?这只是偶然的机会吗?这在我身上经常发生(在对代码进行小改动时)...
可执行文件包含多个部分。如果我没记错的话,每个部分都对齐到 512 字节。
来自https://msdn.microsoft.com/en-us/library/ms809762.aspx:
DWORD FileAlignment
In the PE file, the raw data that comprises each section is guaranteed to start at a multiple of this value. The default value is 0x200 bytes, probably to ensure that sections always start at the beginning of a disk sector (which are also 0x200 bytes in length). This field is equivalent to the segment/resource alignment size in NE files. Unlike NE files, PE files typically don't have hundreds of sections, so the space wasted by aligning the file sections is almost always very small.
编辑:磁盘上的所有部分大小都向上舍入(填充)为 FileAlignment 的倍数。来自 http://www.openwatcom.org/ftp/devel/docs/pecoff.pdf
SizeOfRawData
Size of the section (object file) or size of the
initialized data on disk (image files). For executable
image, this must be a multiple of FileAlignment from
the optional header. If this is less than VirtualSize
the remainder of the section is zero filled. Because
this field is rounded while the VirtualSize field is not
it is possible for this to be greater than VirtualSize as
well. When a section contains only uninitialized data,
this field should be 0.
我认为即使是最后一部分也被填充得如此之多,这样发出部分的链接器代码和加载它们的加载器代码就不必担心最后一部分大小的特殊情况。无论如何,对 trim 最后一部分的优化将是一个毫无意义的优化,因为磁盘扇区(以及该文件系统的更大集群的顶部)有 internal fragmentation 会吞噬任何此类“节省”(来自 trim大部分时间都是最后一节。
我很好奇 - 有时我会更改我的代码,重新编译,然后将我的 exe 或 dll 文件复制到旧版本上,然后看到 Windows 告诉我文件的日期已更改,但是大小保持完全相同。这是为什么?
例如,我使用以下控制台应用程序进行了测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = 2;
Console.WriteLine(a + b);
}
}
}
这生成了一个 5120 字节的 exe 文件(Visual Studio 2012,调试版本)。然后,我将代码更改为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = 2;
int c = 3;
Console.WriteLine(a + b + c);
}
}
}
exe大小完全一样
我看了一下反汇编,它显示了 IL 代码中的差异,所以差异不可能被优化掉:
第一个版本:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 2
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldc.i4.2
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldloc.1
IL_0007: add
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
IL_000e: ret
} // end of method Program::Main
第二个版本:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 a,
[1] int32 b,
[2] int32 c)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldc.i4.2
IL_0004: stloc.1
IL_0005: ldc.i4.3
IL_0006: stloc.2
IL_0007: ldloc.0
IL_0008: ldloc.1
IL_0009: add
IL_000a: ldloc.2
IL_000b: add
IL_000c: call void [mscorlib]System.Console::WriteLine(int32)
IL_0011: nop
IL_0012: ret
} // end of method Program::Main
如果代码体积更大,文件大小怎么可能完全一样?这只是偶然的机会吗?这在我身上经常发生(在对代码进行小改动时)...
可执行文件包含多个部分。如果我没记错的话,每个部分都对齐到 512 字节。
来自https://msdn.microsoft.com/en-us/library/ms809762.aspx:
DWORD FileAlignment
In the PE file, the raw data that comprises each section is guaranteed to start at a multiple of this value. The default value is 0x200 bytes, probably to ensure that sections always start at the beginning of a disk sector (which are also 0x200 bytes in length). This field is equivalent to the segment/resource alignment size in NE files. Unlike NE files, PE files typically don't have hundreds of sections, so the space wasted by aligning the file sections is almost always very small.
编辑:磁盘上的所有部分大小都向上舍入(填充)为 FileAlignment 的倍数。来自 http://www.openwatcom.org/ftp/devel/docs/pecoff.pdf
SizeOfRawData
Size of the section (object file) or size of the initialized data on disk (image files). For executable image, this must be a multiple of FileAlignment from the optional header. If this is less than VirtualSize the remainder of the section is zero filled. Because this field is rounded while the VirtualSize field is not it is possible for this to be greater than VirtualSize as well. When a section contains only uninitialized data, this field should be 0.
我认为即使是最后一部分也被填充得如此之多,这样发出部分的链接器代码和加载它们的加载器代码就不必担心最后一部分大小的特殊情况。无论如何,对 trim 最后一部分的优化将是一个毫无意义的优化,因为磁盘扇区(以及该文件系统的更大集群的顶部)有 internal fragmentation 会吞噬任何此类“节省”(来自 trim大部分时间都是最后一节。