2016-05-26 3 views
2

Моя книга пытается познакомить меня с такими понятиями, как разыменование указателя относительно структур и некоторые странные способы доступа к структурам. Я новичок, и я нашел следующее недоумение относительно кода ниже.Как я могу понять результаты этой программы?

#include <stdio.h> 
#include <time.h> 
void dump_time_struct_bytes(struct tm *time_ptr, int size) { 
    int i; 
    unsigned char *raw_ptr; 

    printf("bytes of struct located at 0x%08x\n", time_ptr); 
    raw_ptr = (unsigned char *)time_ptr; 
    for (i = 0; i < size; i++) 
    { 
     printf("%02x ", raw_ptr[i]); 
     if (i % 16 == 15) // Print a newline every 16 bytes. 
      printf("\n"); 
    } 
    printf("\n"); 
} 
int main() { 

    long int seconds_since_epoch; 
    struct tm current_time, *time_ptr; 
    int hour, minute, second, i, *int_ptr; 

    seconds_since_epoch = time(0); // Pass time a null pointer as argument. 
    printf("time() - seconds since epoch: %ld\n", seconds_since_epoch); 
    time_ptr = &current_time; // Set time_ptr to the address of 
           // the current_time struct. 
    localtime_r(&seconds_since_epoch, time_ptr); 

    // Three different ways to access struct elements: 
    hour = current_time.tm_hour; // Direct access 
    minute = time_ptr->tm_min; // Access via pointer 
    second = *((int *)time_ptr); // Hacky pointer access 
    printf("Current time is: %02d:%02d:%02d\n", hour, minute, second); 
    dump_time_struct_bytes(time_ptr, sizeof(struct tm)); 

    minute = hour = 0; // Clear out minute and hour. 

    int_ptr = (int *)time_ptr; 
    for (i = 0; i < 3; i++) { 
     printf("int_ptr @ 0x%08x : %d\n", int_ptr, *int_ptr); 
     int_ptr++; // Adding 1 to int_ptr adds 4 to the address, 
    } // since an int is 4 bytes in size. 
} 

Выход:

time() - seconds since epoch: 1189311744 
Current time is: 04:22:24 
bytes of struct located at 0xbffff7f0 
18 00 00 00 16 00 00 00 04 00 00 00 09 00 00 00 
08 00 00 00 6b 00 00 00 00 00 00 00 fb 00 00 00 
00 00 00 00 00 00 00 00 28 a0 04 08 
int_ptr @ 0xbffff7f0 : 24 
int_ptr @ 0xbffff7f4 : 22 
int_ptr @ 0xbffff7f8 : 4 
  1. я. Я понимаю, что автор переопределил * time_ptr как указатель на unsigned char, но как ему удалось стать массивом (массив символов, я думаю)? Я думаю, что это может быть связано с тем, что массивы интерпретируются как указатели, указывающие на их 0-й элемент, но я не уверен.

    ii. Во-вторых, каков результат функции dump_time_struct_bytes (сбрасываемые байты)? Я понимаю, что это байты из структуры, но я не знаю, как они должны составлять 4 часа, 22 минуты и 24 секунды в нем (если это вообще так). Кроме того, соответствует ли адрес * time_ptr? Является ли это началом структуры? Если последнее верно, то соответствующие сбрасываемые байты в выходе принадлежат только его первому элементу (tm_sec) или ко всей структуре?

  2. Объяснение для «hacky pointer» было немного странным - почему разыменование конвертированного целочисленного указателя только показывает содержимое первого элемента в структуре tm_sec?

Заранее спасибо.

+0

ii. Не напомнив мне о членах 'struct tm', я могу видеть из шестнадцатеричного дампа, сделанного' printf ("% 02x", raw_ptr [i]); 'что первые 12 байтов - это три 32-битных ints, потому что hex 18 = dec 24 (seconds), hex 16 = dec 22 (минуты), а 4 - часы. –

+0

Благодарим вас за ответ. Однако есть одна вещь, которая кажется немного неясной. Предположим, у меня есть указатель на char 'c'. Является ли указатель теперь указателем на массив символов размером 1? Кроме того, просто для проверки вы упоминали, что указатель на одну структуру является указателем на массив размером один. Таким образом, указание указателя на указатель на char приведет к тому, что размер массива будет больше 1 и будет массивом байтов, ответственным за шестнадцатеричный дамп. Следовательно, если какой-либо указатель, когда придумано таким образом, приведет к этому хорошему распаду байта? –

+0

Вы можете индексировать указатель *, как будто * это массив. C здесь никаких ограничений. Даже если вы указали указатель на ** одиночную ** переменную 'char', вы можете индексировать * точно так, как вам нравится * при условии чтения/записи запрещенной памяти или при написании искажения somthing, которое не вызывает * прямой * ошибки , но создаст проблему через некоторое время ;-) (вот почему ошибки указателя могут быть трудно отследить). Здесь указатель 'char *' был отличен из адреса 'struct', так что его содержимое может быть рассмотрено байтом байтом. –

ответ

3

«Я понимаю, что автор имеет redeclared * time_ptr как указатель на unsigned char, но как ему удалось стать массивом (массив символов, я думаю)?»

Указатели указывают на память. Память - это массив байтов. Количество байтов, на которые указывает указатель, зависит от интерпретации (типа) предмета, на который указывает. Помимо этого простого факта компилятор не выполняет проверку границ в C/C++. Таким образом, по сути каждый указатель является указателем на массив элементов того типа, на который указывает указатель. Таким образом, указатель на unsigned char является указателем на массив однобайтовых символов. Указатель на структуру - это указатель на массив элементов, каждый из которых имеет размер одной структуры.

Так указатель на единую структуру IS указатель на массив размера 1. Ничто в языке не предотвращает плохой код и пытается получить доступ к элементу в следующем местоположении.

Это и сила, и проклятие указателей. И источник многих ошибок и проблем безопасности в C/C++. Кроме того, вы можете сделать много классных вещей на языке эффективно.

«С большой силой приходит большая ответственность».

Таким образом, этот код интерпретирует указатель структуры сначала как массив байтов и печатает шестнадцатеричный дамп, а затем как массив целых чисел. При обработке указателя как int * операция одиночного приращения перемещается на 4 байта.

Следовательно, первый элемент - 0x00000018 (малое значение для четырех байтов: 18 00 00 00). 0x18 гекс 24.

Второе целое является 0x00000016 (обратным порядком байтов 16 00 00 00) = 22.

И т.д.

Обратите внимание, что int * перемещается на 4 байта, потому что в вашем конкретном компиляторе sizeof(int) == 4. «int» - это особый тип и может изменять размер на основе вашего компилятора. Если у вас был другой компилятор (скажем, для встроенного микроконтроллера), то sizeof (int) может быть 2, а целые числа будут распечатываться как 24, 0, 22 (при условии, что тот же самый блок памяти).

Is the size of C "int" 2 bytes or 4 bytes?

=== В ответ на комментарий ===

«(Accidentally прокомментировал где-то еще) Спасибо за ваш ответ. Тем не менее, есть одна вещь, которая кажется немного неясно. Скажем, у меня есть указатель на полукокс «с». Является ли указатель теперь указатель на массив символов размера 1?

YES. байтовый массив из одного байта.

Кроме того, просто чтобы убедиться, вы» вы указали, что указатель на одну структуру указатель на массив размером один.

ДА, но в этом случае размер одного элемента в массиве равен sizeof(mystruct), что, вероятно, больше, чем один байт.

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

ДА.

Следовательно, должен ли любой указатель, когда typecasted таким образом, приведет к этому хорошему распаду байта?

ДА. Вот как работают байты/память.

Еще одна вещь о ключевое слово sizeof(type). sizeof(type) сообщает размер (в байтах) экземпляра type. sizeof(variable) эквивалентен sizeof (тип переменной). Это имеет тонкое поведение, когда переменная является указателем или массивом. Например:

char c = '0' // in memory this is the single byte 0x30 
char str[] = { 0x31, 0x32, 0x00 }; // an array of bytes 0x31, 0x32, 0x00 

sizeof(char) == sizeof(c) == 1 
sizeof(str) == 3 // compiler knows the array was initialized to 3 bytes 
sizeof(p) == 4 // assuming your compiler is using 32-bit pointers. On a 64-bit machine this would be 8. 

char* p = &c; // note that assigning a pointer to the address of a variable requires the address-of operator (&) 

sizeof(*p) == 1 // this is the size of the thing pointed to. 

p = str; // note that assigning an ARRAY variable name to a pointer does not require address-of (because the name of an array IS a pointer - they *are* the same type in all ways except with respect to sizeof() where sizeof() knows the size of an initialized array.) 

sizeof (*p) == 1; // even though p was assigned to str - an array - sizeof still returns the answer based on the type of the thing p is pointing to - in this case a single char. This is subtle but important. p points to a single character in the array. 

// Thus at this point, p points to 0x31. 
p++; // p advances in memory by sizeof(*p), now points at 0x32. 
p++; // p advances in memory by sizeof(*p), now points at 0x00. 
p++; // p advances in memory by sizeof(*p), now points BEYOND THE ARRAY. 

ВАЖНО - Поскольку указатель был выдвинут в конце прошлого массива, в этой точке р указывает на возможно недействительна памяти или это может указывать на какой-либо другой случайной величины в памяти. Это может привести к сбою (в случае недопустимой памяти) или повреждению ошибки и памяти (и вероятной ошибке безопасности), если она указывает на «действительную» память, которая не используется, как ожидалось. В этом конкретном случае, когда предполагается, что переменные живут в стеке, он указывает на переменную или, возможно, на обратный адрес функции. В любом случае, выходя за пределы массива, BAD. ОЧЕНЬ ОЧЕНЬ ПЛОХО. и КОМПЬЮЛЕР НЕ ОСТАВИВАЕТ ВАС !!!

Также, кстати, sizeof НЕ является функцией. Он оценивается компилятором во время компиляции на основе таблицы символов компилятора. Поэтому нет никакого способа, чтобы получить размер массива, выделенный так:

char* p = malloc(sizeof(char)*100); 

компилятор не понимает, что вы выделить 100 байт, потому что таНос является функцией времени выполнения. (действительно, 100 обычно является переменной с изменяющимся значением). Поэтому sizeof(p) вернет размер указателя (или 4 или 8, как упоминалось ранее), и sizeof(*p) вернет sizeof(char), что равно 1. В таком случае код должен помнить, сколько памяти было выделено в отдельной переменной (или в другой путь - динамическое распределение - это отдельная тема).

Другими словами, SizeOf() работает только для типов и для статически инициализированных массивов (те, которые инициализируются в коде), например, такие:

char one[] = { 'a' }; 
char two[] = "b"; // using the string quotes results in a final zero-byte being automatically added. So this is an array of 2 bytes. 
char three[3] = "c"; // the specified size overrides the string size, so this produces an array of 'c', 0, <uninitialized> 
char bad[1] = "d"; // trying to put 2 bytes in a 1 byte-bag. This should generate a compiler error. 
+0

(случайно прокомментировал в другом месте) Спасибо за ваш ответ. Однако есть одна вещь, которая кажется немного неясной. Предположим, у меня есть указатель на char 'c'. Является ли указатель теперь указателем на массив символов размером 1? Кроме того, просто для проверки вы упоминали, что указатель на одну структуру является указателем на массив размером один. Таким образом, указание указателя на указатель на char приведет к тому, что размер массива будет больше 1 и будет массивом байтов, ответственным за шестнадцатеричный дамп. Следовательно, если какой-либо указатель, когда придумано таким образом, приведет к этому хорошему распаду байта? –

+0

См. Дополнительное объяснение выше. – Brad

+0

'char bad [1] =" d ";' является законным в C, он инициализирует первый элемент '' d'' –

1

Указатель struct tm *time_ptr является typecasted к char *, это просто означает, что память, на которую он указывает, теперь будет обрабатываться как последовательность из 1 байтовых данных. Это основная концепция, используемая для воздушной атаки указателя, тип указателя определяет, сколько байтов будет перемещаться указателем при его увеличении. Так как это указатель char, то приращение его будет двигаться вперед только одним байтом, и вы можете увидеть, что дамп памяти выдает байты байтом.

Во втором случае тип указателя равен (int*), указывая на то же место памяти, которое теперь будет обрабатывать память как последовательность sizeof(int) (в зависимости от платформы размер может меняться). В этом случае это 4 байта. Теперь вы можете видеть, что 4-байтная группа 0x00 00 00 18 равна 24 десятичным. Аналогично, 0x00 00 00 16 равно 22 в десятичном значении, а 0x00 00 00 04 равно 4 в десятичной системе. (Учитывайте здесь утверждение).

2
unsigned char *raw_ptr; 
raw_ptr = (unsigned char *)time_ptr; 

Это создает указатель типа unsigned char и инициализируется с указателем на struct tm указатель (выполненного с помощью литья).

but how did it manage to become an array (character array, I think)

time_ptr не изменился. Программе говорят, чтобы посмотреть на ту же ячейку памяти, что и time_ptr, но рассмотрите ее как массив из unsigned char типов.

I think that this might be to do with the fact that arrays are interpreted as pointers which point to their 0th elements, but I am not sure.

Типы массивов распадаются на указатели. Так что да, массивы представлены указателями. Однако указатель не обязательно должен быть связан с 0-м индексом, но так будет, когда сначала создается массив.

Secondly, what is the output from the dump_time_struct_bytes function (the dumped bytes)?

Да. Существует не byte типа, так что char или unsigned char часто используется.

Also, what does the address of *time_ptr correspond to? Is it the start of the structure?

Да.

If the latter is true, do the corresponding dumped bytes in the output belong only to its first element (tm_sec) or to the whole structure ?

Вся структура, потому что второй параметр size был инициализирован с помощью sizeof(struct tm) (т.е. всех байт, содержащих этот типа).

The explanation for the "hacky pointer" was a bit weird- why does dereferencing a converted integer pointer solely reveal the contents of the first element in the structure- tm_sec?

It seems that the first data member is tm_sec and it is of type int. Поэтому указатель на struct tm указывает на ту же память, что и для хранения tm_sec. Таким образом, расположение памяти отлито от int*, так как tm_sec имеет тип int, и мы имеем дело с указателем на него. Затем разыменовывается значение этого адреса (когда он обрабатывается/рассматривается как int, а не struct tm).


Примечание: для произвольных 4 байт. Что значит? Если они рассматриваются как 32-битный целочисленный тип без знака, создается определенное значение. Если их рассматривать как 32-битный тип с плавающей точкой, может быть создано другое значение.Кастинг - это способ заставить конкретный «вид» байтов независимо от того, что представляют собой байты.

Смежные вопросы