LINUX.ORG.RU

Почему тут нет ошибки?

 


0

1
int* a();

int main(){

int* x = a();
int b = *x;
printf("%d\n",b);

}

int* a(){

int x= 25;
return &x;

}

Смотрел ролики на ютубе у них программа вылетала. Правда там на виндовс было. Ожидал тоже сегметацию увидеть. А она спокойно отработала.

Перемещено leave из general

★★

Последнее исправление: tyamur (всего исправлений: 1)

Ошибка в программе не означает, что она будет «вылетать» каждый раз. Более того, некоторые некорректные программы будут работать стабильно на конкретной платформе из-за особенностей платформы.

i-rinat ★★★★★
()

UB

$ cat seg.c 
int* a();

int main() {
    int* x = a();
    int b = *x;
    printf("%d\n",b);
}

int* a() {
    int x= 25;
    return &x;
}


$ gcc seg.c 
seg.c: In function ‘main’:
seg.c:6:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
    6 |     printf("%d\n",b);
      |     ^~~~~~
seg.c:6:5: warning: incompatible implicit declaration of built-in function ‘printf’
seg.c:1:1: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’
  +++ |+#include <stdio.h>
    1 | int* a();
seg.c: In function ‘a’:
seg.c:11:12: warning: function returns address of local variable [-Wreturn-local-addr]
   11 |     return &x;
      |            ^~


$ ./a.out 
Segmentation fault (core dumped)

https://stackoverflow.com/a/12380788
https://stackoverflow.com/a/14872593

EXL ★★★★★
()
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от i-rinat

uname -a Linux localhost 3.10.72+ #1 SMP PREEMPT Sun Nov 20 21:39:10 CST 2016 armv7l Android

tyamur ★★
() автор топика

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

Разве что какой-то слишком ръяный компилятор 100500 проверок добавил в код.

Kroz ★★★★★
()
Ответ на: комментарий от Kroz

знаю почему может сучиться segmentation fault

вот поэтому:

int b = *x;

вообще-то ничего не запрещает компилятору вернуть 0, мало того, gcc так и делает

anonymous
()

Ты видишь суслика? И я не вижу, а он есть!

Если программа работает без ошибок, то это не значит, что в ней их нет.

anonymous
()

У меня сегфолтится на Debian/gcc. А вообще не обязана.

Санитайзерами прогони, они ловят.

И пости в тематические разделы такое.

anonymous
()

привет. я тоже пытаюсь научиться программировать.

скажи пожалуйста, что значит у тебя

return &x;


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

return *x;

return &x;

[/cod]
Assembler
()
Ответ на: комментарий от Assembler

Я так понимаю операция * применима только к указателям. Она возвращает значение которое хранится по этому адресу. А операция & применима как к обычным переменным так и к указателям и возвращает ууазатель.

tyamur ★★
() автор топика
Ответ на: комментарий от Assembler

return &x возвращает указатель на переменую х; Адрес этой переменой указывает на область стэка.

tyamur ★★
() автор топика
Ответ на: комментарий от RedEyedMan666

Что именно? Вопрос был про сегментацию. А это я обьяснил @Assembler

tyamur ★★
() автор топика
Ответ на: комментарий от tyamur

Мне кажется, тебе стоит попробовать писать проще, чтобы не наделать ошибок, Best Practices почитать соответствующий.

RedEyedMan666
()
Ответ на: комментарий от anonymous

вот поэтому:
int b = *x;

И что?

In computing, a segmentation fault (often shortened to segfault) or access violation is a fault, or failure condition, raised by hardware with memory protection, notifying an operating system (OS) the software has attempted to access a restricted area of memory (a memory access violation).

И почему вдруг память, в которой был локальная переменная x вдруг стала restricted?

В моем понимании программа может дотянуться до любой памяти, при условии что эта память аллоцирована под эту программу. Я могу допустить, что нельзя модифицировать память с кодом (то, что есть сегмент кода в реально режиме). Но почему область данных (пусть даже в стеке) сначала принадлежала программе, а потом перестала принадлежать, я понять не могу. Это ж нужно по выходу из функции специально флаги проставлять или что-то другое делать. Но в Си это лишнее ИМХО, ну, или по крайней мере должно включаться/выключаться в флагах компилятора.

P. S. А вообще похоже на оптимизацию. Потенциально, компилятор может понять, что функция a() не делает ничего, и просто исключить ее из кода. Или хранить переменную x в регистрах. Тогда, можно залезть restricted area of memory.

Если это так, то на Микрософт эффект уйдет если отключить все оптимизации. А в gcc эффект может появиться, если включить максимальные оптимизации.

Kroz ★★★★★
()
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от Kroz

И что?

И то. До этого в этот х попадает 0, потому что компилятору ничего не мешает вернуть 0 в функции a, вместо указателя на стек. Что gcc и делает.

И какая же ошибка будет при попытке дереференса нулевого указателя? Неужели segment violation, да не может быть.

anonymous
()
Ответ на: комментарий от Kroz

И почему вдруг память, в которой был локальная переменная x вдруг стала restricted?

Кто сказал, что компилятор вернет указатель на стек?

anonymous
()
Ответ на: комментарий от anonymous
  • т.е. функция a, с какой радости возвращая &x компилятор так и запишет, что возвращаем указатель на стек?

Это UB. Кто во что горазд. Clang возвращает, а вот GCC нет.

anonymous
()
Ответ на: комментарий от anonymous

И кстати GCC поступает правильнее. Этой переменной при выходе из функции больше не существует, всё. Логичнее будет вернуть нулевой указатель.

anonymous
()
Ответ на: комментарий от tyamur

С чем его и поздравляю. А gcc возвращает 0 даже при -O0. Отсюда sigsegv. Изначально вопрос был именно в этом, типа не понятно, откуда там вдруг может sigsegv прилететь. Я объяснил.

anonymous
()
Ответ на: комментарий от anonymous

Все понял. Значить они использовали gcc.

tyamur ★★
() автор топика
Ответ на: комментарий от Assembler

Никак. Попытка дереференса нулевого указателя вызывает сигнал segment violation и программа завершается.

anonymous
()
Ответ на: комментарий от anonymous

спасибо. очень интересная тема была

автору тоже спасибо

Assembler
()
Ответ на: комментарий от tyamur

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

спасибо

Assembler
()
Ответ на: комментарий от anonymous

И то. До этого в этот х попадает 0, потому что компилятору ничего не мешает вернуть 0 в функции a, вместо указателя на стек.

А с чего бы ему возвращать 0, если я его прошу указатель на x, что есть вполне себе валидной операцией пока я внутри функции.

Kroz ★★★★★
()
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от Assembler

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

Указатель - это и есть адрес ячейки памяти.
& может быть применен к переменным и функциям.

Kroz ★★★★★
()
Ответ на: комментарий от Kroz

А с чего бы ему возвращать 0, если я его прошу указатель на x

UB, компилятор может делать что хочет, хоть форматировать тебе винчестер.

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

https://gcc.godbolt.org/z/g7MZVc

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от tyamur

-O1

main:
    subq    $8, %rsp
    movl    0, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    movl    $0, %eax
    addq    $8, %rsp
    ret
a:
    movl    $0, %eax
    ret

-O2

main:
    movl    0, %eax
    ud2
a:
    xorl    %eax, %eax
    ret
anonymous
()
Ответ на: комментарий от Kroz

с функциями что-то мудрите. имя функции -это уже адрес. взять адрес адреса я не могу.

void foo(){};

&foo //  это бессмысленно. 
Assembler
()
Ответ на: комментарий от Kroz

Кстати, Rust возвращает адрес локальной переменной:

https://gcc.godbolt.org/z/hD7og6

https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=b0a88d379703d5ab586d76eec509ed46

Паскаль тоже, паскаль даже 25 в x записывает, а Rust забивает на это дело, поэтому rust 0 печатает, а паскаль 25:

https://gcc.godbolt.org/z/yoRbmq

Прикольно смотреть оптимизации в разных языках :)

Так что если вы не любите UB, выбирайте даже не Rust, а Паскаль. Он будет выполнять именно то, что вы написали.

fsb4000 ★★★★★
()
Ответ на: комментарий от Assembler

и правда!

void foo(){};

void(*p)()=foo;

void(*p)()=&foo; 

не имеют отличия на Ассемблер.

компилируем с флагом -S два файла в котором есть амперсант перед именем функции и файл в котором нет амерсанта.

сравниваем их diff -y и не видим отличий.

Assembler
()
Ответ на: комментарий от Assembler

А теперь галоперидолу и спать.

anonymous
()

Cмотрел ролики на ютубе у них программа вылетала.

Нужно помнить что после выхода из функции всё что она содержала «уничтожается» сразу/потом/никогда не важно, всё становится для тебя ничем! Она умерла, её нет. Всё. Ничего не осталось. Для себя надо считать именно так.

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

- int x= 25;
+ static int x = 25;

Компилируй так

cc -Wall -Werror -pedantic src.c ;

Компилятор твой друган.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от Kroz

А с чего бы ему возвращать 0

При подыхании любого объекта, значения указателей, указывавших на него, становятся indeterminate-значениями

6.2.4 Storage durations of objects

2 The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

В DR 453 комитет постановил, что indeterminate value может быть представлен любым набором битов (в т.ч. всеми нулями). Это кажется, что GCC возвращает нулевой указатель. На самом деле возвращается indeterminate value, представленный всеми нулевыми битами. Что похоже на представление нулевого указателя. (Комитет также постановил, что из совпадения всех битов в представлении значения не следует одинаковость этих значений, в частности, нулевой указатель и indeterminate pointer value могут быть представлены одинаково, но это 2 разных значения).

anonymous
()
Ответ на: комментарий от anonymous

Это кажется, что GCC возвращает нулевой указатель. На самом деле возвращается indeterminate value, представленный всеми нулевыми битами

Иезуиты проклятые.

anonymous
()

Смотрел ролики на ютубе у них программа вылетала. Правда там на виндовс было. Ожидал тоже сегметацию увидеть. А она спокойно отработала.

не будет никакой сегментации. в данном случае a() вернула адрес со стека. из своего фрейма. поскольку стек является легальным сегментом для чтения записи, то и читать этот адрес и писать по нему - легально. и поскольку никаких других функций после a() не вызывалось, то там так и лежит число 25. попробуй после a() вызвать такую же aa(), но читать по адресу взятому из a():

int* a(){
  int x= 25;
  return &x;
}

int* aa(){
  int x= 77;
  return &x;
}

int main(){
  int* x = a();
  aa(); //просто вызвали
  int b = *x;
  printf("%d\n",b); //получишь 77
}

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

ребята, мы и так тут бреда понаписали. так-то кто понимает поржет и все.



mov $0, %eax; сюда компилятор кладет ноль. зачем? это отдельная тема.


 mov (%eax),   ;  в этом регистре лежить ноль и сейчас скобки говорят, что будет косвенная адресация.  а так как там ноль, то и будет крах программы. 
Assembler
()

А потом пишут, дескать, Си не плохой, плохие рукожопы, у которых нет памяти Вассермана помнить стандарт и отсутствует опыт работы со всеми популярными конпеляторами. Поделка уровня продуманности жопоскрипта.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Assembler

ребята, мы и так тут бреда понаписали. так-то кто понимает поржет и все.

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

alysnix ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.