9

我一直在深入研究 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(data, 0, sizeof(data)); 行,则最右边的字节是正确的,但其中一些仍然具有前导 1。

有没有人解释为什么

A)使用 memset 清除我的数组会导致函数的不正确(编辑:不准确)表示,并且

解决方案:是由于使用了 memset(data, 0, sizeof(data)),而不是 memset(data, 0, 20 * sizeof(unsigned char))。内存没有完全设置,因为它只查看指针的大小而不是整个数组的大小。

B)这个字节存储在内存中是什么?整数?字符?我不太明白这里发生了什么。(澄清:我将使用什么类型的指针来遍历内存中的此类数据?)

解决方案:我很笨。我忘记了 unsigned 关键字,这就是整个问题的来源:(

任何帮助将不胜感激 - 我在四处搜索时找不到任何东西。

尼尔

PS:我的直接想法是,这是 x86 的指令不以字节或半字节边界结束的结果。但这并没有多大意义,也不应该引起任何问题。

感谢 Will 指出我的 char 类型错误。它应该是无符号字符。但是,我仍然对如何访问单个字节感到好奇。

4

5 回答 5

6

我相信您chars正在将符号扩展到整数的宽度。通过在打印时显式转换值,您可能会得到更接近您想要的结果。

于 2012-12-31T20:41:10.183 回答
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”,以便它始终打印两个字符 [它不会修复'负' 数字以 ffffff89 左右出现 - 这unsigned由数据指针上的 固定。

x86 中的所有指令都以字节边界结束,编译器通常会插入额外的“填充指令”以确保分支目标与 4、8 或 16 字节边界对齐以提高效率。

于 2012-12-31T20:46:35.263 回答
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`
}
于 2012-12-31T20:41:02.330 回答
1

问题出在您要打印的代码中。

从数据数组加载一个字节。(一个字节 == 一个字符)

字节被转换为“int”,因为这是编译器知道“printf”想要的。为此,它将字节扩展为 32 位双字。这就是打印为十六进制的内容。(这意味着高位为 1 的字节将转换为 32 位值,其中 8-31 位全部设置。这就是您看到的 ffffffxx 值。)

在这种情况下,我要做的是自己转换它:

 printf( "%x\n", ((int)data[i] && 0xFF) );

然后它将正确打印。(如果您正在加载 16 位值,您将使用 0xffff 进行 AND。)

于 2012-12-31T20:42:42.130 回答
0

打印看起来很奇怪,因为您正在打印带符号的值,因此它们正在被符号扩展。

但是,打印的功能也略有不同。看起来不是用字符串的地址加载 EAX,并将其填充到堆栈中,而是直接存储地址。

push        ebp  
mov         ebp,esp  
sub         esp,18h  
mov         dword ptr [esp],8048610h  
call        <printf>  
leave  
ret  

至于为什么当您在代码的其他地方进行看似良性的更改时它会发生变化 - 好吧,它是允许的。这就是为什么最好不要依赖未定义的行为。

于 2012-12-31T20:45:59.127 回答