git -P diff
Или --no-pager
.
BTW: сохранить цвет с кошкой
git diff --color=always | cat
Технически этот код выходит далеко за рамки того, что определяет стандарт C, поэтому он может делать что угодно . Он делает огромное количество предположений, на которые не имеет права, и эти предположения определенно не всегда верны. Тем не менее, я могу предложить весьма вероятное объяснение того, почему вы видите результат, который вы делаете:
Вы правы до того момента, когда вы скопировали код функции g ()
в память, занятая переменной локального массива a
.
Чтобы понять следующую строку, вам нужно знать немного о том, как функции обычно вызываются в обычных архитектурах на основе стека. Когда функция вызывается, параметры помещаются в стек, затем адрес возврата помещается в стек, и выполнение переходит к начальной точке функции. Внутри функции указатель предыдущего кадра помещается в стек, после чего освобождается место для локальных переменных. Стеки имеют тенденцию увеличиваться вниз в памяти (от высоких адресов к низким адресам), хотя это не так на всех распространенных архитектурах.
Таким образом, когда основной вызов функции f ()
, стек изначально выглядит так (указатель кадра и указатель стека - это два регистра ЦП, содержащих адреса ячеек в стеке):
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
STACK POINTER -> | return address | &x - 1
|-------------------------|
| ... | (lower addresses)
Пролог функции затем сохраняет указатель кадра вызывающей функции и перемещает указатель стека, чтобы освободить место для локальных переменных в f ()
. Итак, когда код C в f ()
начинает выполняться, стек теперь выглядит примерно так:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
| saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
| return address | &x - 1
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| a[99] | &a[99]
| a[98] | &a[98]
| ... | ...
STACK POINTER -> | a[0] | &a[0]
| ... | (lower addresses)
Что такое указатель кадра? Он используется для ссылки на локальные переменные и параметры в функции. Компилятор знает, что при выполнении f ()
адрес локальной переменной a
равен всегда FRAME_POINTER - 100 * sizeof (int)
, и адрес параметра x
равен FRAME_POINTER + sizeof (FRAME_POINTER) + sizeof (RETURN_ADDRESS)
. Все локальные переменные и параметры могут быть доступны как фиксированное смещение от указателя кадра, независимо от того, как указатель стека перемещается по мере выделения и освобождения пространства стека.
В любом случае, вернемся к коду. Когда эта строка выполняется:
x = *(&x-1) ;
Она копирует значение, которое хранится в памяти на 1 целое число меньше, чем x
, в x
. Если вы посмотрите мой ASCII-арт, вы увидите, что это обратный адрес. Итак, что на самом деле выполняет это:
x = RETURN_ADDRESS;
Следующая строка:
*(&x-1) = (int)(&a) ;
Затем устанавливает адрес возврата на адрес массива a
. Это действительно говорит:
RETURN_ADDRESS = &a;
Приведение требуется, потому что вы обрабатываете адрес возврата как int
, а не как указатель (так что на самом деле этот код будет работать только только архитектуры, где int
имеет тот же размер, что и указатель - это НЕ будет работать, например, в 64-битных системах POSIX!).
Код C в функции f ()
сейчас завершается, и эпилог функции освобождает локальные переменные (путем перемещения указателя стека назад) и восстанавливает указатель кадра вызывающего объекта. На данный момент стек выглядит так:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
STACK POINTER -> | return address | &x - 1
|-------------------------|
| saved frame pointer |
|-------------------------|
| a[99] | &a[99]
| a[98] | &a[98]
| ... | ...
| a[0] | &a[0]
| ... | (lower addresses)
Теперь функция возвращается, перейдя к значению RETURN_ADDRESS, но мы установили для него значение и
, поэтому вместо возврата к тому месту, откуда он был вызван, он переходит к значению начала массива a
- теперь он выполняет код из стека. Здесь вы скопировали код из функции g ()
, так что код (по-видимому) успешно работает. Обратите внимание: поскольку указатель стека был перемещен назад над массивом здесь, любой асинхронный код, который выполняется с тем же стеком (например, сигнал UNIX, поступающий в неподходящий момент), перезапишет код!
Итак, вот что такое стек. теперь выглядит как начало g ()
перед прологом функции:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
STACK POINTER -> | int x (parameter) |
|-------------------------|
| return address |
|-------------------------|
| saved frame pointer |
|-------------------------|
| a[99] |
| a[98] |
| ... |
| a[0] |
| ... | (lower addresses)
Пролог для g ()
затем устанавливает кадр стека как обычно, выполняет его и раскручивает это, Это вызовет некоторые странные эффекты - если вы вызовете другую функцию из main
на этом этапе, содержимое a
будет изменено!
Надеюсь, вы (и другие) узнали кое-что ценное из этого обсуждения, но важно помнить, что это похоже на технику программирования «Пятиконечная ладонь взрывающегося сердца» - НИКОГДА не используйте ее в реальной системе. Новая подархитектура, компилятор или даже просто другие флаги компилятора могут и будут изменять среду выполнения в достаточной степени, чтобы заставить этот вид слишком умного наполовину кода полностью потерпеть неудачу всеми видами восхитительных и забавных способов.
Надеюсь, вы (и другие) узнали что-то ценное из этого обсуждения, но важно помнить, что это похоже на технику программирования «Пятиконечная ладонь взрывающегося сердца» - НИКОГДА не используйте ее в реальной системе . Новая подархитектура, компилятор или даже просто другие флаги компилятора могут и будут изменять среду выполнения в достаточной степени, чтобы заставить этот вид слишком умного наполовину кода полностью потерпеть неудачу всеми видами восхитительных и забавных способов.
Надеюсь, вы (и другие) узнали что-то ценное из этого обсуждения, но важно помнить, что это похоже на технику программирования «Пятиконечная ладонь взрывающегося сердца» - НИКОГДА не используйте ее в реальной системе . Новая подархитектура, компилятор или даже просто другие флаги компилятора могут и будут изменять среду выполнения в достаточной степени, чтобы заставить этот вид слишком умного наполовину кода полностью потерпеть неудачу всеми видами восхитительных и забавных способов.
Хорошо, это всего лишь возможное объяснение того, что происходит, поскольку, как уже упоминалось, когда вы перезаписываете " важные адреса памяти: все может случиться.
С учетом сказанного, похоже, что происходит что-то вроде этого:
Опять же, это все предположения - все зависит от компилятора, его параметров и платформы, на которой вы его используете.
Хорошо, я разобрался, как он возвращается в основной (см. Комментарий к ответу Тала).
Вам нужно знать, как работает стек, в частности, на процессоре Intel .
В основном стек выглядит так:
stacktop: 1234 - the a variable, locals are normally on the stack)
в начале f (x) он выглядит так
stacktop: 1234 - main's a
-1 1234 - the argument to f(), pushed onto the stack
-2 ret_addr - points to the printf in main, where f() will go when it's finished
-3 a[99]
-4 a[98]
...
-101 a[1]
-102 a[0]
Стеки растут сверху вниз.
Код "(& x-1)" указывает на stacktop-2 в этом случае, поскольку & x является адресом параметра, переданного в f (), который равен stacktop-1.
После копирования функции g () в массив a [] вы затем устанавливаете переданное значение of x равным ret_addr, поэтому стек будет:
stacktop: 1234 - main's a
-1 ret_addr - the modified value of x
-2 ret_addr - points to the printf in main, where f() will go when it's finished
-3 a[99]
...
Затем вы устанавливаете (& x-1) в a []:
stacktop: 1234 - main's a
-1 ret_addr - the modified value of x
-2 &a[0] - points to the copy of g
-3 a[99]
...
Затем функция завершается. Это перемещает указатель стека на stacktop-2, освобождая выделенные локальные переменные (в данном случае a []), а затем переходит к тому, что находится в стеке, в данном случае & a [0] (stacktop-2), и уменьшает размер стека.
Это указывает на копию g (). g () выполняется, а затем завершается, переходит к адресу на вершине стека (stacktop-1, в данном случае теперь это указатель на printf в main) и снова уменьшает стек.
У этого много проблемы.
Вызов функции с аргументами "по значению" не делает аргументы изменяемыми функцией. Простые целые числа передаются по значению, когда вы вызываете f (a);
из main (), что не позволяет функции f
изменять значение a
, он получает только значение. Если вы хотите изменить исходную переменную, вам нужно вызвать ее по ссылке, например f (& a);
, после изменения функции, чтобы она принимала указатель, конечно.
Это немного ... бессмысленно спорить о том, чего ожидать, когда вы делаете неопределенные вещи, например, перезаписываете память. Также попытка скопировать код функции с ее адреса не очень безопасна.
Вам следует взглянуть на эту классическую статью , в которой объясняется механизм переполнения стека.
Эта программа предполагает, что порядок параметров в стеке следующий:
[адрес возврата] [x]
Итак, & x-1 - это адрес адреса возврата
В a у вас есть вся функция 'g'. При изменении 'x' x указывает на исходную точку возврата другое изменение X и перезапись его указателем на «a» должно изменить возвращаемое значение основной функции, но на самом деле я не уверен насчет вывода, это будет зависеть от используемых оптимизаций.