Как функции хранятся в памяти?

я углубился в Linux и C, и мне интересно, как функции хранятся в памяти. У меня есть следующая функция:

void test(){
    printf( "testn" );
}

достаточно просто. Когда я запускаю objdump в исполняемом файле с этой функцией, я получаю следующее:

08048464 <test>:
 8048464:       55                      push   %ebp
 8048465:       89 e5                   mov    %esp,%ebp
 8048467:       83 ec 18                sub    x18,%esp
 804846a:       b8 20 86 04 08          mov    x8048620,%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( "%xn", 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 ) ); строка, то самый правый байт правильный, но некоторые из них все еще имеют ведущие 1s.

есть ли у кого-нибудь объяснение, почему

A) использование memset для очистки моего массива приводит к неправильному (edit: неточному) представлению функции и

решение: было связано с использованием memset( data, 0, sizeof( data ) ), а не memset( data, 0, 20 * sizeof (unsigned char ) ). Память не была полностью настроена, потому что он смотрел только на размер указателя, чем на размер всего массива.

B) что этот байт хранится как в памяти? ИНЦ? чар? Я не совсем понимаю, что здесь происходит. (уточнение: какой тип указателя я бы использовал для перемещения таких данных в памяти?)

решение: я тупой. Я забыл ключевое слово unsigned, и вот откуда взялась вся проблема: (

любая помощь была бы очень признательна - я ничего не мог найти при поиске вокруг для этого.

Нил

PS: моя непосредственная мысль заключается в том, что это результат x86 с инструкциями, которые не заканчиваются на байтовой или полубайтовой границе. Но это не имеет большого смысла и не должно вызывать никаких проблем.

спасибо Уиллу за указание моей ошибки с типом char. Это должен был быть неподписанный символ. Однако мне все еще интересно, как получить доступ к отдельным байтам.

5 ответов


вот гораздо более простой случай кода, который вы пытались сделать:

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-байтовым границам для эффективности.


Я считаю, что ваш chars идет знак до ширины целого числа. Вы можете получить результаты ближе к тому, что вы хотите, явно задавая значение при его печати.


проблема заключается в вашем коде для печати.

один байт загружается из массива данных. (один байт == один символ)

байт преобразуется в' int', так как это то, что компилятор знает' printf ' хочет. Для этого знак расширяет байт до 32-битного двойного слова. Это то, что печатается как hex. (Это означает, что байт с высоким битом одного будет преобразован в 32-битное значение с битами 8-31. Это значения ffffffxx, которые вы видите.)

что я делаю в этом случае стоит преобразовать его самому:

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

затем он будет печатать правильно. (Если бы вы загружали 16-битные значения, вы бы и с 0xffff.)


ответ на B) байт хранится как байт в памяти. Местоположения памяти ровно 1 байт, содержащихся в памяти.(байт unsigned char)

подсказка: Возьмите хорошую книгу о компьютерной организации (Мой любимый один Карл Хамачар и понять много о том, как память внутренне представлена)

в коде:

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  

что касается того, почему он изменяется, когда вы делаете кажущиеся доброкачественными изменения в другом месте кода - ну, это разрешено. Вот почему хорошо не полагаться на неопределенное поведение.