2009-11-06 3 views
4

При вызове функции в Linux (или OS X, если на то пошло), может ли пользователь изменить значения аргументов в стеке? Я полагал, что поскольку вызывающий абонент очищает их, они должны содержать те же значения после вызова функции. Однако я обнаружил, что GCC с -O2 изменяет параметры, которые были переданы ему в стеке. Я также искал документацию, включая соглашения о вызовах System V i386, но не смог найти окончательный ответ на этот вопрос.Соглашения о вызовах C и переданные аргументы

Вот пример кода, который я отлаживал.

pushl %eax  # %eax = 0x28 
call _print_any 
popl %eax 
       # %eax is now 0x0a 

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

+0

«Модификация» каким образом? Вы понимаете, что, если вы не сообщите компилятору об этом, нет оснований ожидать, что он не будет гадать со значениями в стеке, чтобы завершить его оптимизацию? –

+0

Как работает код C и как работают соглашения о вызовах, могут быть совершенно разными. Ниже приведены соглашения о вызове System V C для i386, если вам интересно. Тем не менее, он имеет несколько неопределенность в отношении некоторых вещей ... http://www.sco.com/developers/devspecs/abi386-4.pdf –

ответ

3

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

В примере, который вы опубликовали, GCC выпустил инструкцию popl %eax, чтобы освободить пространство, занимаемое параметром в стеке. Все, что действительно нужно сделать, это добавить 4%% esp (стек на x86 растет вниз в памяти), а выполнение инструкции popl %eax - это самый короткий и быстрый способ сделать это. Если компилятору необходимо было освободить 20 значений, вероятно, он изменил бы% esp напрямую, вместо того, чтобы выдавать 20 popl инструкций.

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

+0

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

+0

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

1

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

Если вы говорите на C или C++ POD, очистка просто изменяет указатель стека.

Если вы говорите о C++ с деструктором, вызывающий отвечает за вызов деструктора, но деструкторы для общих классов должны быть записаны для очистки любого значения.

1

В стандарте C вызывающий абонент может изменять значения своих аргументов всем, что он хочет, но вызывающий не увидит изменений.

Что может сбивать с толку, так это то, что если вы передаете POINTER в значение, тогда вызывающий может изменить это значение, разыменовывая указатель, но если вызываемый на самом деле изменяет сам указатель, вызывающий объект не увидит это изменение.

Небольшой нит: стандарт C не требует, чтобы реализация даже имела стек.

+0

Я не думаю, что это относится к проблемам ОП. Исходный вопрос касается не только языка C, но и конкретной реализации (как, если я не изменяю его, почему компилятор делает это?). Я не буду спускать вниз, но я думаю, что вы ошиблись с этим. –

+0

Да на практике аргументы никогда не могли быть фактически в каком-либо стеке, это было бы в названном регистре (это особенно верно на платформах с большим количеством регистров - например, MIPS). – joemoe

+0

Я не говорю о аргументах в C, я говорю о вызове функций C из сборки и на уровне машины. –

0

Если вы пройдете по значению:

call do_it(to_it); 

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

Если вы пройдете по ссылке:

call do_it(&to_it); 

Тогда адрес переменной передается. Любые изменения, вызванные вызываемой переменной, будут относиться к исходной переменной в вызывающей программе.

+0

Я не говорю об этом на языке C или C++ (или не передавая по ссылке/значению). Я говорю о фактических параметрах стека во время выполнения на уровне машины и о том, могут ли быть изменены эти местоположения стека. –