Как функции хранятся в памяти?
я углубился в 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
что касается того, почему он изменяется, когда вы делаете кажущиеся доброкачественными изменения в другом месте кода - ну, это разрешено. Вот почему хорошо не полагаться на неопределенное поведение.