我一直在深入研究Linux和C,我很好奇函数是如何存储在内存中的。 我有以下功能:
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
如果我选择省略memset(数据,0,sizeof(数据));行,然后最右边的字节是正确的,但其中一些仍然有前导1。
有没有人解释为什么
A)使用memset清除我的数组会导致函数的错误(编辑:不准确)表示,并且
解决方案:是由于使用了memset(数据,0,sizeof(数据)),而不是memset(数据,0,20 * sizeof(unsigned char))。内存没有完全设置,因为它只查看指针大小而不是整个数组的大小。
B)这个字节存储在内存中是什么?整型?炭?我不太明白这里发生了什么。 (澄清:我将使用什么类型的指针在内存中遍历这些数据?)
解决方案:我很蠢。我忘记了unsigned关键字,这就是整个问题的来源:(
非常感谢任何帮助 - 在搜索时我找不到任何东西。
尼尔
PS:我的直接想法是,这是x86具有不以字节或半字节边界结束的指令的结果。但这并没有多大意义,也不应该造成任何问题。
感谢Will用char类型指出我的错误。它应该是unsigned char。我仍然很好奇如何访问单个字节。
答案 0 :(得分:6)
我相信您的chars
符号扩展为整数的宽度。您可以通过在打印时显式地转换值来获得更接近您想要的结果。
答案 1 :(得分:4)
以下是您尝试执行的代码的更简单的情况:
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”,以便它始终打印两个字符[它不会' t将'负'数字固定为ffffff89左右 - 用数据指针上的unsigned
固定。
x86中的所有指令都以字节边界结束,编译器通常会插入额外的“填充指令”,以确保分支目标与4,8或16字节边界对齐以提高效率。
答案 2 :(得分:1)
答案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`
}
答案 3 :(得分:1)
问题出在您的代码中。
从数据阵列加载一个字节。 (一个字节==一个字符)
该字节转换为'int',因为这是编译器知道'printf'想要的。为此,它将符号扩展为32位双字。这就是以十六进制打印出来的内容。 (这意味着高位为1的字节将转换为32位值,位8-31全部设置。这是您看到的ffffffxx值。)
在这种情况下我做的是自己转换它:
printf( "%x\n", ((int)data[i] && 0xFF) );
然后它将正确打印。 (如果您正在加载16位值,那么您将与0xffff一起使用。)
答案 4 :(得分:0)
打印看起来很奇怪,因为您打印的是有符号值,因此它们会被标记扩展。
然而,正在打印的功能也略有不同。它看起来不是用字符串的地址加载EAX,而是将它填充到堆栈中,而是直接存储地址。
push ebp
mov ebp,esp
sub esp,18h
mov dword ptr [esp],8048610h
call <printf>
leave
ret
为什么当你在代码中的其他地方做出看似良性的改变时它会发生变化 - 好吧,它是允许的。这就是为什么不依赖未定义的行为是好的。