LINUX.ORG.RU

с / с++ нубский вопрос..

 


0

1
int main()
{ 
    int *x;

    *x = 5;

return 0;
}

1. Cкажите пожалуйста чем такое может грозить? 2. Правильно ли я понимаю что если нету никаких new, delete, malloc-ов, и прочих умных указателей то мы всего лишь создали указатель типа int. Будет ли он кудато укзывать если мы не присвоили ему явно адресс какойто переменной?(наверное да он же всетаки указывает на кокойто адресс), или же undefined behavior? 3. Потом мы всего лишь разименовываем указатель и записываем по этому адрессу число ( к примеру 5 ). 4. Вроде как все происходить без участия «кучи» правильно. 5. Насколько легально работать со стеком через указатели?

Извините за туповатые вопросы.. Спасибо.



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

4. Вроде как все происходить без участия «кучи» правильно.

1. Cкажите пожалуйста чем такое может грозить?

Segmentation fauilt. Ты пытаешься разыменовать (почти) нулевой указатель. Под дебагом у тебя там будут нули, под релизом - мусор.

2. Правильно ли я понимаю что если нету никаких new, delete, malloc-ов, и прочих умных указателей то мы всего лишь создали указатель типа int.

Да

Будет ли он кудато укзывать если мы не присвоили ему явно адресс какойто переменной?

В космос.

3. Потом мы всего лишь разименовываем указатель и записываем по этому адрессу число ( к примеру 5 ).

См. объяснение к пункту 1.

4. Вроде как все происходить без участия «кучи» правильно.

Вроде как ты не делать правильный предложений.

5. Насколько легально работать со стеком через указатели?

Мм? Когда ты выделяешь переменные на стеке - то это так:

main()
{
   int a;
   int b;
}

На куче так:

main()
{
   int * a = malloc(512);
   int * b = calloc(256);
}

Заметь, что ты тогда все равно выделишь на стеке 8 байт под сами указатели.

sambist ★★
()

1. Падением программы при разыменовании. 2. Создали указатель на стеке который указывает на произвольный адрес (UB, такой указатель может инициализироваться чем угодно, мусор там), если сделать его static — будет указывать на nullptr, NULL, 0. 3. Читаем или пишем по произвольному адресу, 99% он вне адресного пространства программы — ОС такую программу тут и прикроет, иначе будем работать с мусором. 4. Указатель произвольный, вероятности попасть в произвольное место кучи процесса никак не будет противоречить стандарту, но ты уже наверно понял, что так делать не нужно. 5. Легально работать со стеком через указатели так:

#include <cstdio>

int main() {
    int x = 31; // число на стеке
    int *p = &x; // указатель на стеке указывает на то число на стеке
    printf("%p\n", (void*)p); // значение адреса числа на стеке лежит в значении указателя (на стеке)
    printf("%d\n", *p); // то число можно читать через этот указатель
    *p += 11; // и изменять
    printf("%d\n", *p);
}
motto
()

Спасибо огромное за разяснение.. Я просто тут баловался с gcc и тут такое.. в принципи и clang и gcc скомпилировали такой код. С флагом -Wall gcc даже честно предупредил: warning: 'x' is used uninitialized in this function [-Wuninitialized]

clang даже посоветовал присвоить nullptr раз уж вам нужен такой «пустой » указатель. Немогу понять только одно зачем он вообще разрешает такое скомпилировать. Может в C есть нечто для обратной совместимости чего я не знаю (и С без такой конструкции не может обойтись).. Есть ли вообще место в коде для таких «пустых указателей». С «кучей» как бы понятно там без этого не обойтись нужно в рантайме выделятть, освобождать память. А вот со стеком, компилятор же знает о всех таких переменных так же как и о обявленных функциях к примеру. При чем компилятор ведь даже диагностирует что я херню пишу (при -Wall) правда, но говорит делай что хчешь. При этом в дебаге SEGFALT, при попытке cout, в обычном режиме муссор.

CunMun
() автор топика
$ cat q.c
int main()
{ 
    int *x;

    *x = 5;

return 0;
}

$ gcc -c -O2 q.c -o q.o
$ objdump -d q.o

q.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text.startup:

0000000000000000 <main>:
   0:	31 c0                	xor    %eax,%eax
   2:	c3                   	retq   
$ 
i-rinat ★★★★★
()
Ответ на: комментарий от CunMun

Есть ли вообще место в коде для таких «пустых указателей».

Автоматический объект без инициализации в си и с++ себя так ведёт вообще, указатель уже в частности. То есть, например

    struct AB { int a, b; };
    AB ab;
    printf("%d %d\n", ab.a, ab.b);

в a и b может оказаться что угодно. Оправдывается это zero overhead-ом — static или явная инициализация сделают своё дело, иначе время на инициализацию не тратится. Точно так же malloc не инициализирует память которую выделяет, так что в ней тоже мусор, для выделения с зануленим есть calloc. Для структур/массивов это ещё может иметь смысл при передачи ссылки/указателя на них в сторонний инициализатор — тогда нет смысла инициализировать стек/кучу первый раз. А случай указателей скорее всего наследие C89 — int *p должен быть в начале блока (значение для инициализации может появиться после), for (int *p = ... не было (нужно было int *p; for (p = ...).

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

Немогу понять только одно зачем он вообще разрешает такое скомпилировать.

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

pef-secure
()
Ответ на: комментарий от CunMun

Пиши всегда -Wall -Werror -Wextra, и все будет классно!

Я вообще простым скриптом копирую вот такую заготовку:

PROGRAM =
LDFLAGS =
SRCS =
CC = gcc
DEFINES = -D_XOPEN_SOURCE=501
CXX = gcc
CFLAGS = -Wall -Werror -Wextra $(DEFINES)
OBJS = $(SRCS:.c=.o)
all : $(PROGRAM) clean
$(PROGRAM) : $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(PROGRAM)

# some addition dependencies
# %.o: %.c
#        $(CC) $(LDFLAGS) $(CFLAGS) $< -o $@
#$(SRCS) : %.c : %.h $(INDEPENDENT_HEADERS)
#        @touch $@

clean:
	/bin/rm -f *.o *~
depend:
	$(CXX) -MM $(CXX.SRCS)

в случае мелких велосипедов. А в случае больших велосипедов пользуюсь cmake'ом, но все равно обязательно указываю эти флаги.

Только так можно гарантировать, что ты не махнешь рукой: "а, ругается — да и хрен с ним!"

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от pef-secure

Да уж. Я так понял те кто утерждает что учить Си перед С++ вредно, может гдето и правы. Но чтобы получать удовольствие от этого плюсового «садо-мазо» полюбому придется выучить Си. Иначе никакой фреймворк не спасет мои ноги.

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

Я так понял те кто утерждает что учить Си перед С++ вредно, может гдето и правы

Неправильно ты понял. С++ вообще учить не нужно. С хватит.

Но если ты собираешься не задачи решать, а "окошки" рисовать, то С изучать тебе не стоит — не сможешь в С++ (будешь постоянно пытаться говнометоды недоязычка заменять сишными функциями).

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от mix_mix

А ты какой-то странный.

Просто это ответ на следующий вопрос: «а почему когда я компилирую такое, сегфолта не происходит?»

i-rinat ★★★★★
()

int *x;

А на что указывает *x? Правильно, хрен знает на что...

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от Eddy_Em

Я кстати тоже смотрю на это все и в какомто замешательстве.. Вроде если не брать stl то плюсы не так уж и много накрутили поверх си. С другой стороны то что они накрутили в stl, скажем так «неокрепшему уму» (я гдето о себе) не так то просто и вкурить. Вот смотрю я видеозаписи с последнего cppcon 2014. И сложилось такое впечетление что ребята из коммитета (проффесура) упражняются добавляя разые параметр_паки для шабланов, лямбды. В то время как ребята в продакшене вобще эти темплейты не используют ( не все кончено ), все живут своей жизнью, пишут свои велосипеды, стандартную библиотеку стараются обходить стороной (а я думал им только стримы в ней нравятся). Из последнего что подхватили так это rvalue reference, видимо действительно в производительности вытаскивает. Хотя может они это все и внедряют но только медленно, х-з.

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

Я так понял те кто утерждает что учить Си перед С++ вредно, может гдето и правы.

Не правы, для нормального знания C++ тебе все равно придется точно знать границы где Си, а где C++. Кроме того, Си намного проще C++.

Что бы хорошо писать на крестах, нужно знать как выглядит каждая концепция в чистом виде (C++ очень кривой язык, и состоит из кучи попыток усидеть на 3-4 стульях).

Kuzy ★★★
()
Ответ на: комментарий от i-rinat
$ cat q.c
#include <stdio.h>
main(void){ 
    int *x;
    *x = 5;
    printf("%d\n", *x);
    return 0;
}

$ gcc -O2 q.c -o q
$ ./q
5
$ gcc -c -O2 q.c -o q.o
$ objdump -d q.o

q.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text.startup:

0000000000000000 <main>:
   0:   48 83 ec 08             sub    $0x8,%rsp
   4:   ba 05 00 00 00          mov    $0x5,%edx
   9:   be 00 00 00 00          mov    $0x0,%esi
   e:   bf 01 00 00 00          mov    $0x1,%edi
  13:   31 c0                   xor    %eax,%eax
  15:   e8 00 00 00 00          callq  1a <main+0x1a>
  1a:   31 c0                   xor    %eax,%eax
  1c:   48 83 c4 08             add    $0x8,%rsp
  20:   c3                      retq   
$
TheAnonymous ★★★★★
()
Ответ на: комментарий от TheAnonymous

Dead code elimination отлично работает. Может быть, я просто не замечал, и изменения произошли раньше, но недавно я делал замеры скорости, и gcc 4.9 выбросил все циклы из программы, так как я вообще не использовал результат вычислений. Раньше такое только за icc наблюдалось.

i-rinat ★★★★★
()
Ответ на: комментарий от TheAnonymous
max@ideapad max % cat q.c  
#include <stdio.h>
main(void){ 
    int *x;
    *x = 5;
    printf("%d\n", *x);
    return 0;
}
max@ideapad max % clang -c -O2 q.c -o q.o
q.c:2:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
main(void){ 
^~~~
1 warning generated.
max@ideapad max % objdump -d q.o

q.o:     формат файла elf64-x86-64


Дизассемблирование раздела .text:

0000000000000000 <main>:
   0:   0f 0b                   ud2

Это вообще как вот это всё скомпилировалось в ud2? o_O

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

В случае явного UB программа вместо «авось пронесёт» должна кидать исключение. Вот clang и вставляет ud2, и правильно делает. К слову, нечто подобное есть в явном виде в сорцах ядра:

#ifdef CONFIG_DEBUG_BUGVERBOSE
#define BUG()                           \
__asm__ __volatile__(  "ud2\n"         \
                        "\t.word %c0\n" \
                        "\t.long %c1\n" \
                         : : "i" (__LINE__), "i" (__FILE__))
#else
#define BUG() __asm__ __volatile__("ud2\n")
#endif

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

Рбеята спасибо всем кто ответил. Вы мне помогли немного пролить свет на эту тьомную тему. Как я понял действительно, если мы всего лишь обявили некую переменную (тоже касается и пользовательских типов) то мы всего лишь зарезервировали место под нее, и до тех пор пока мы ее не проинициализируем там может находится все что угодно. Тоже самое касаетсья и указателя, если мы обявили указатель на обект, то грубо говоря мы лишь обявили «перемнную» способную хранить адресс указывающий на объект определенного типа. И пока мы не привяжим этот указатель к реальному объекту (грубо говоря проинициализируем указатель присвоив ему адрес какого нибудь объекта соответствующего типа) он будет содержать тоже все что угодно(произвольный адрес как отмечал motto всего адресного пространства ОС). Я так подозреваю (хотя не уверен) что если этот адрес случайно окажется в адресном пространстве программы, то возможно будет даже разименовать его и произвести по этому адрессу записть. И возможно даже обращатся к этим данным через этот указатель. На счет этого я правда не уверен если кто разяснит было бы очень интерестно (или ссылку даст какую нибудь).

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

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

Считай, что угадал.

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

Интро: https://ru.wikipedia.org/wiki/Виртуальная_память
Для самых маленьких: http://habrahabr.ru/post/211150/
Для детишек побольше: http://lwn.net/Articles/250967/

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

Спасибо с удовольствием почитал. Особенно понравилось (правда уже с другой странички):

http://en.wikipedia.org/wiki/Memory_management_unit

The IBM System/370 has had an MMU since the early 1970s. It was initially known as a dynamic address translation (DAT) box.

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

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

И даже стронно что до сих пор нет хардварной JVM

была уже. не прижилась.

pef-secure
()

Учи лучше Go. Там поправили многие корявости C, и спецификация всего 70 страниц.

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

Я даже не знаю, в детали не вдавался. Глянул первую попавшуюся ссылку:

http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=go&lang2=gpp

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

Дальше читаю спецификацию go:

Program initialization and execution

The zero value

When memory is allocated to store a value, either through a declaration or a call of make or new, and no explicit initialization is provided, the memory is given a default initialization. Each element of such a value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, «» for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

These two simple declarations are equivalent:

After

type T struct { i int; f float64; next *T }
t := new(T)

the following holds:

t.i == 0
t.f == 0.0
t.next == nil

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

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

Немогу понять только одно зачем он вообще разрешает такое скомпилировать.

Потому что не инициализированный указатель может быть валидным кодом, например:

int* p;
init(&p); // функция выделяет память.

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

В то время как ребята в продакшене вобще эти темплейты не используют

Кто тебе такое сказал? Где надо и могут - ещё как используют. Не говоря уже о том, что многие вещи помогают при написании библиотек (да хотя бы стандартной или буста) и ты их можешь «не использовать» явно, но в итоге твой код это использовать будет.

стандартную библиотеку стараются обходить стороной

Да ну?

Всё это очень сильно зависит от специфики. Я вот могу сказать, что ещё как используют.

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

Это далеко не всегда возможно. И clang тоже не всегда это делает.

Разумеется, только когда он может это полностью доказать.

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