我一直深入研究Linux和C,我很好奇函数如何存储在内存中。 我有以下function:
void test(){ printf( "test\n" ); }
很简单。 当我有这个函数的可执行文件运行objdump时,我得到以下内容:
08048464 <test>: 8048464: 55 push %ebp 8048465: 89 e5 mov %esp,%ebp 8048467: 83 ec 18 sub $0x18,%esp 804846a: b8 20 86 04 08 mov $0x8048620,%eax 804846f: 89 04 24 mov %eax,(%esp) 8048472: e8 11 ff ff ff call 8048388 <printf@plt> 8048477: c9 leave 8048478: c3 ret
哪一个看起来不错 有趣的部分是当我运行下面的一段代码:
int main( void ) { char data[20]; int i; memset( data, 0, sizeof( data ) ); memcpy( data, test, 20 * sizeof( char ) ); for( i = 0; i < 20; ++i ) { printf( "%x\n", data[i] ); } return 0; }
我得到以下(这是不正确的):
55 ffffff89 ffffffe5 ffffff83 ffffffec 18 ffffffc7 4 24 10 ffffff86 4 8 ffffffe8 22 ffffffff ffffffff ffffffff ffffffc9 ffffffc3
如果我select忽略memset(data,0,sizeof(data)); 那么最右边的字节是正确的,但其中一些仍然是领先的1。
有没有人有任何解释为什么
A)使用memset清除我的数组会导致函数的错误(编辑:不准确)表示
解决scheme:是由于使用memset(data,0,sizeof(data)),而不是memset(data,0,20 * sizeof(unsigned char))。 内存没有完全设置,因为它只看到一个指针的大小,而不是整个数组的大小。
B)这个字节存储在内存中是什么? 整型? 炭? 我不太明白这里发生了什么事。 (说明:我将使用什么types的指针来遍历内存中的数据?)
解决scheme:我很笨。 我忘了unsigned关键字,这就是整个问题来自哪里:(
任何帮助将不胜感激 – 我找不到任何东西时search这个。
尼尔
PS:我的直接想法是,这是x86的结果不是以字节或半字节边界结束的指令。 但是这并没有什么意义,也不应该引起任何问题。
感谢Will用chartypes指出我的错误。 它应该是无符号的字符。 我仍然好奇如何访问单个字节。
这是你试图做的代码更简单的例子:
int main( void ) { unsigned char *data = (unsigned char *)test; int i; for( i = 0; i < 20; ++i ) { printf( "%02x\n", data[i] ); } return 0; }
我所做的更改是删除多余的缓冲区,而不是使用指针来测试,使用unsigned char而不是char,并将printf更改为使用“%02x”,以便始终打印两个字符[它不会修复'fowfff89左右'出现'负数' – 这与数据指针上的unsigned
固定。
x86中的所有指令都以字节为边界,编译器通常会插入额外的“填充指令”,以确保分支目标与4,8或16字节的边界对齐,以提高效率。
我相信你的chars
被扩展到一个整数的宽度。 打印时,您可以通过明确地转换值来使结果更接近您想要的结果。
问题出在您的代码打印。
从数据数组中加载一个字节。 (一个字节==一个字符)
该字节被转换为“int”,因为这是编译器知道'printf'想要的。 要做到这一点,它的符号将字节扩展到一个32位的双字。 这就是打印出来的十六进制。 (这意味着高位为1的字节会被转换为32位的值,所有的位都设置为8-31,这就是你所看到的ffffffxx值。
我在这种情况下做的是自己转换它:
printf( "%x\n", ((int)data[i] && 0xFF) );
然后它会正确打印。 (如果你正在加载16位值,你会与0xffff)。
答案B)字节存储在内存中的字节。 内存位置中包含1个字节的内存位置(一个字节是unsigned char
)
提示:拿起一本关于计算机组织的好书(我最喜欢的是卡尔·哈马瑟(Carl Hamachar)的一本书,并理解关于内存是如何在内部表现的)
在你的代码中:
memset( data, 0, sizeof( data ) );// must be memset(data,0,20); memcpy( data, test, 20 * sizeof( char ) ); for( i = 0; i < 20; ++i ) { printf( "%x\n", data[i] );// prints a CHARACTER up-casted to an INTEGER in HEX representation, hence the extra `0xFFFFFF` }
打印看起来很奇怪,因为您正在打印有符号的值,所以它们正在被扩展。
然而,正在打印的功能也稍有不同。 它看起来不是用字符串的地址加载EAX,而是将其填充到堆栈中,直接存储地址。
push ebp mov ebp,esp sub esp,18h mov dword ptr [esp],8048610h call <printf> leave ret
至于为什么当你在代码中的其他地方做出看似良性的改变时,为什么会发生改变 – 好吧,这是允许的。 这就是为什么不依靠未定义的行为。