почему возвращать значение в стеке функций небезопасно - PullRequest
4 голосов
/ 03 марта 2011

Я натолкнулся на следующий абзац, читая Брюса Экеля ... где он пытался объяснить, почему функция не может возвращать значение в стеке

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

Не могли бы вы помочь мне понять текст, выделенный жирным курсивом?

Ответы [ 4 ]

2 голосов
/ 03 марта 2011

Предположим, что у вас есть следующий стек вызовов в вашем приложении:

  1. Основная процедура
  2. Локальные переменные Function1
  3. Локальные переменные Function2 <- STACK POINTER </li>

В этом случае main вызывает function1, а function1 вызывает function2.

Теперь предположим, что function2 вызывает function3, а возвращаемое значение function3 возвращается в стеке:

  1. Основная процедура
  2. Локальные переменные Function1
  3. Локальные переменные Function2
  4. Локальные переменные Function3, включая возвращаемое значение <- STACK POINTER </li>

Функция 3 сохраняет возвращаемое значение в стеке, а затем возвращает. Возвращение означает, снова уменьшая указатель стека, поэтому стек становится таким:

  1. Основная процедура
  2. Локальные переменные Function1
  3. Локальные переменные Function2 <- STACK POINTER </li>

Видите ли, стекового фрейма функции 3 здесь больше нет.

Ну, на самом деле я немного соврал. Кадр стека все еще там:

  1. Основная процедура
  2. Локальные переменные Function1
  3. Локальные переменные Function2 <- STACK POINTER </li>
  4. Локальные переменные Function3, включая возвращаемое значение

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

Но, если есть прерывание, ПОСЛЕ возврата функции 3, но ДО того, как функция 2 получит возвращаемое значение из стека, мы получим следующее:

  1. Основная процедура
  2. Локальные переменные Function1
  3. Локальные переменные Function2
  4. Локальные переменные функции прерывания <- STACK POINTER </li>

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

Поэтому возвращать возвращаемое значение в стеке небезопасно.

Проблема похожа на проблему, показанную в этом простом фрагменте кода C:

char *buf = (char *)malloc(100*sizeof(char *));
strcpy (buf, "Hello World");
free (buf);
printf ("Buffer is %s\n",buf);

В большинстве случаев память, которая использовалась для buf, все еще будет содержать содержимое «Hello World», но она может пойти ужасно неправильно, если кто-то сможет выделить память после вызова free, но до вызова printf. Один из таких примеров - в многопоточных приложениях (и мы уже столкнулись с этой проблемой внутри), как показано здесь:

THREAD 1:                                  THREAD 2:
---------                                  ---------
char *buf = (char *)malloc(100);
strcpy (buf, "Hello World");
free (buf);
                                           char *mybuf = (char *)malloc(100);
                                           strcpy (mybuf, "This is my string");
printf ("Buffer is %s\n",buf);

printf is Thread 1 теперь может печатать «Hello World» или «This my string». Все может случиться.

2 голосов
/ 03 марта 2011

Он просто пытается объяснить, почему вы не должны возвращать указатель или ссылку на локальную переменную.Потому что она исчезает, как только функция возвращается!

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

1 голос
/ 03 марта 2011

Самая важная часть этого абзаца:

Если вы пытаетесь вернуть значения на стек ниже адреса возврата (...)

Другими словами, не возвращайте указатели к данным, которые действительны только в рамках этой функции.

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

И возврат по значению полностью поддерживается в C ++ уже некоторое время. В противном случае многие детали реализации STL просто не будут работать должным образом.

1 голос
/ 03 марта 2011

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

Допустим, мы находимся на процессоре, где указатель стека хранится в регистре с именем SP, и он увеличивается "вверх".

  1. Ваш код загружается и приходит к вызову функции. На этом этапе мы скажем, что SP равен 100.
  2. Функция вызывается, и ваша функция принимает два однобайтовых аргумента. Эти два байта аргументов помещаются в стек, и ... и это важная часть - адрес кода, из которого вы вызвали функцию (скажем, это 4 байта). Теперь SP равен 106. Адрес для возврата - SP = 100, а два ваших байта - 104 и 105.
  3. Допустим, функция модифицирует один из этих аргументов (SP = 105) как способ возврата измененного значения
  4. Функция возвращается, стек возвращается туда, где она была (SP = 100), и продолжает работу.

  5. В идеальном мире в системе больше ничего не происходит, и ваша программа имеет абсолютный контроль над процессором ... Пока вы не сделаете что-то еще, требующее стека, это значение SP = 105 останется там " навсегда».

  6. Однако с прерываниями нет никаких гарантий, что что-то еще не возникнет. скажем, аппаратное прерывание попадает в ваше приложение. Это означает немедленный переход к подпрограмме обслуживания прерываний, поэтому текущий адрес того места, где был процессор, когда попадание прерывания помещается в стек (4 байта), теперь SP равен 103. Допустим, этот ISR вызывает другие подпрограммы, что означает больше обратные адреса помещаются в стек. Итак, теперь SP равно 107 ... ваше первоначальное значение 105 не было перезаписано

  7. в конечном итоге эти ISR вернутся, управление вернется к вашему коду, и SP снова станет равным 100 ... ваше приложение пытается получить это значение SP = 105, к счастью, не зная, что оно было сбито ISR, и теперь вы работаете с неверными данными.

...