Учебник C - Интересно о `int i = * (int *) & s;` - PullRequest
5 голосов
/ 18 сентября 2011

Прорабатывая учебник C

#include <stdio.h>

int main() {
  short s = 10;
  int i = *(int *)&s; // wonder about this
  printf("%i", i);
  return 0;
}

Когда я говорю C, что адрес s является целым, разве он не должен читать 4 байта?

Начиная с самой левой стороны 2 байта s.В каком случае это не является критически опасным, так как я не знаю, что он читает, поскольку шорт только назначил 2 байта?мне?

Ответы [ 6 ]

5 голосов
/ 18 сентября 2011
  1. Никогда не делайте этого
  2. Выбросьте учебник, если он учит / проповедует.

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

На самом деле это опасно и нарушает Правило строгого псевдонима [Подробности ниже] и вызывает Неопределенное поведение .
Компилятор должен выдать вам предупреждение, подобное этому.

warning: dereferencing type-punned pointer will break strict-aliasing rules

И вы всегда должны слушать свой компилятор, когда он выдает это предупреждение.


[Detail]

Строгий псевдоним - это допущение, сделанное компилятором C (или C ++), что разыменование указателей на объекты разных типов никогда не будет ссылаться на одну и ту же ячейку памяти (то есть на псевдонимы друг друга).

Исключением из правила является символ *, который может указывать на любой тип.

3 голосов
/ 18 сентября 2011

Прежде всего, никогда не делают этого .

Относительно того, почему он не падает: поскольку s является локальным, он размещается в стеке. Если short и int имеют разные размеры в вашей архитектуре (что не является заданным значением), то , вероятно, закончится чтением еще нескольких байтов из памяти, которая находится на той же странице памяти, что и стек ; так и не будет нарушения прав доступа (даже если вы будете читать мусор).

Возможно.

1 голос
/ 18 сентября 2011

По сути, вы правы в том смысле, что, поскольку вы обращаетесь к указателю int *, он извлечет 4 байта вместо 2, зарезервированных для хранения 's', и результирующий контент не будет идеальным отражением того, что ' s 'действительно означает.

Однако, скорее всего, это не приведет к сбою, так как 's' находится в стеке, поэтому в зависимости от того, как ваш стек расположен в данный момент, вы, скорее всего, будете читать данные, помещенные во время пролога 'main' функции ...

Смотрите, чтобы программа аварийно завершилась из-за недопустимого доступа к памяти чтения, вам нужно получить доступ к области памяти, которая не отображается, что вызовет «ошибку сегментации» на уровне пользовательского мира, в то время как «ошибка страницы» на уровне ядра. Под «отображением» я подразумеваю, что у вас есть известное отображение между областью виртуальной памяти и физической областью памяти (такое отображение обрабатывается операционной системой). Вот почему, если вы получите доступ к NULL-указателю, вы получите такое исключение, потому что на уровне пользовательского мира нет действительного отображения. Действительное сопоставление обычно дается вам, вызывая что-то вроде malloc () (обратите внимание, что malloc () - это не системный вызов, а умная оболочка, которая управляет вашими блоками виртуальной памяти). Ваш стек не является исключением, поскольку он является просто памятью, как и все остальное, но некоторая предварительно отображенная область уже создана для вас, поэтому при создании локальной переменной в блоке вам не нужно беспокоиться о ее расположении в памяти, поскольку она обрабатывается для вы и в этом случае вы не получаете доступ достаточно далеко, чтобы достичь чего-то не нанесенного на карту.

Теперь допустим, что вы делаете что-то подобное:

short s = 10;
int *i = (int *)&s;
*i = -1;

Тогда в этом случае ваша программа с большей вероятностью завершится сбоем, поскольку в этом случае вы начнете перезаписывать данные. В зависимости от данных, к которым вы прикасаетесь, эффект этого может варьироваться от безобидного неправильного поведения программы до сбоя программы, если, например, вы перезаписываете адрес возврата, помещенный в стек ... Повреждение данных является для меня одним из самых сложных (если не самым сложным) ) категория ошибок, с которой нужно иметь дело, поскольку ее влияние может повлиять на вашу систему случайным образом с недетерминированным шаблоном и может произойти спустя много времени после фактического выполнения исходных оскорбительных инструкций.

Если вы хотите больше узнать об управлении внутренней памятью, вы, вероятно, захотите изучить Управление виртуальной памятью в проектах операционных систем.

Надеюсь, это поможет,

1 голос
/ 18 сентября 2011

Прежде всего, все адреса имеют одинаковый размер, и если вы используете 64-битную архитектуру, каждый символ *, короткий * или int * будет иметь 8 байтов. При использовании звезды перед амперсандом эффект отменяется, поэтому * & x семантически эквивалентно просто x.

1 голос
/ 18 сентября 2011

Нет, это не приведет к сбою вашей программы, однако она будет читать часть других переменных (или, возможно, мусора) в стеке.Я не знаю, из какого учебника вы это получили, но такой код страшен.

1 голос
/ 18 сентября 2011

Это опасное и неопределенное поведение, как вы и сказали.

Причина, по которой он не падает на 32 (или 64) битных платформах, заключается в том, что большинство компиляторов выделяют по меньшей мере 32 бита для каждой переменной стека.Это ускоряет доступ, но, например, на 8-битном процессоре вы можете получить данные мусора в старших битах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...