2013-08-01 1 views
1

Для прямых C и GCC, почему строка с целлой не повреждена здесь?C Stack-Allocated String Scope

#include <stdio.h> 

int main(int argc, char *argv[]) 
{ 
    char* str_ptr = NULL; 

    { 
     //local to this scope-block 
     char str[4]={0}; 
     sprintf(str, "AGH"); 

     str_ptr = str; 
    } 

    printf("str_ptr: %s\n", str_ptr); 

    getchar(); 
    return 0; 
} 

| ---- ВЫХОД ----- |

str_ptr: AGH 

| -------------------- |

Код a link приведен в код, скомпилированный и выполненный с использованием онлайн-компилятора.

Я понимаю, что если str был строковым литералом, str был бы сохранен в bss (по существу, как статический), но sprintf (ing) в буфер, распределенный по стекам, я думал, что строковый буфер будет чисто стек- (и, следовательно, адрес не имеет смысла после выхода из блока видимости)? Я понимаю, что для избыточной записи памяти по указанному адресу может потребоваться дополнительное распределение стека, но даже с использованием рекурсивной функции до тех пор, пока не произойдет переполнение стека, я не смог повредить строку, на которую указывает str_ptr.

FYI Я делаю свое тестирование в VS2008 C проекте, хотя GCC, похоже, демонстрирует такое же поведение.

+0

Этот код не является C90. –

ответ

1

Скорее всего, компилятор выполняет какие-то простые оптимизации, в результате чего строка остается в одном месте в стеке. Другими словами, компилятор позволяет стеку расти для хранения 'str'. Но он не сокращает стек в объеме основного, потому что этого не требуется.

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

#include <stdio.h> 

char * str_ptr = NULL; 
void onstack(void) 
{ 
    char str[4] = {0}; 
    sprintf(str,"AGH"); 
    str_ptr = str; 
} 

int main(int argc, char *argv[]) 
{ 

    onstack(); 
    int x = 0x61626364; 
    printf("str_ptr: %s\n", str_ptr); 
    printf("x:%i\n",x); 
    getchar(); 
    return 0; 
} 

С gcc -O0 -std=c99 strcorrupt.c Я получаю случайный выход на первом printf. Он будет варьироваться от машины к машине и архитектуры до архитектуры.

+0

Итак, можете ли вы рассказать о разнице между локальным блоком области и переместить это в функцию? – user2640330

+1

Когда компилятор генерирует код для выхода из функции, он должен убрать стек обратно до его первоначального размера.В противном случае вызывающая функция увидит поврежденный стек. Это называется конвенцией о вызове. Почти все соглашения позволяют вызывающему абоненту предположить, что стек не изменился после вызова функции. Информация остается только в стеке, если у вызываемого есть возвращаемое значение. Обратите внимание, что функция onstack объявлена ​​недействительной. Он не помещает ничего в стек для вызывающего абонента. Чтобы узнать больше, прочитайте: http://en.wikipedia.org/wiki/X86_calling_conventions –

+0

Так как в принципе, поскольку «соглашение о вызове» не применимо к сфере основной функции, стек не требует восстановления исходное состояние pre-main (если функция pre-main была функцией). Это означает, что строка остается нетронутой в точке, где происходит печать? – user2640330

2

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

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

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

Таким образом, в некоторых компиляторах C с определенными наборами параметров компиляции и за исключением отдельных фаз луны массив символов str не будет перезаписан вызовом printf, хотя время жизни переменной истекло.

+0

Этот ответ был бы отличным, если бы первое предложение было более похоже на следующие строки: «Хотя носовые ящерицы являются популярной частью фольклора C, код, который опирается на неопределенное поведение, может, по-видимому, работать». – Sebivor

+0

@undefinedbehaviour: Ваше возражение против слов «кажется, игнорирует очевидные ошибки»? В противном случае, я не уверен, в чем разница .... – rici

+0

Мое возражение против слов «неопределенное поведение может быть вообще чем угодно». Это означает, что даже поведение, которое четко определено, может быть неопределенным; Очень страшно! 'int x = 0;', например, не является неопределенным поведением. «ни оправдание, чтобы его не исправить», может также лучше читаться как «ни оправдание, чтобы пренебрегать его исправлением». – Sebivor