LINUX.ORG.RU

Неинициализированные переменные в C++ при использовании разных опций оптимизации g++

 , , ,


0

4

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

#include <iostream>

void func1();

int main()
{
    std::cout << "    main()" << std::endl;;
    
    int i;
    std::cout << "int\ti = " << i << std::endl;
    
    double d;
    std::cout << "double\td = " << d << std::endl;
    
    unsigned i2;
    std::cout << "unsign.\ti2 = " << i2 << std::endl;
    long int li;
    std::cout << "l int\tli = " << li << std::endl;
    
    func1();
    return 0;
}

void func1()
{
    std::cout << "    func1()" << std::endl;;
    int a;
    std::cout << "int\ta = " << a << std::endl;
    bool b;
    std::cout << "bool\tb = " << b << std::endl;
}


Если его собрать без оптимизации: c++ -o prog1 prog1.cc, то бинарник выводит

$ ./prog1
    main()
int     i = 0
double  d = 6.95273e-310
unsign. i2 = 0
l int   li = 4197312
    func1()
int     a = 32535
bool    b = 180

Т.е. в теле функции main() переменные типу int, short int, unsigned int, вроде как, инициализируются нулями. Значение остальных переменных, как и положено, неопределенно.

Если же его собрать с оптимизацией (-O1, -O2 или -O3): c++ -O2 -o prog1 prog1.cc, то бинарник уже выводит

$ ./prog1
    main()
int     i = 0
double  d = 0
unsign. i2 = 0
l int   li = 0
    func1()
int     a = 0
bool    b = 0
Выходит что при включении оптимизации, вообще, все переменные инициализируются(?).

Собственно, вопрос: почему переменные сами инициализируются при включении оптимизации? И почему если собрать без оптимизации, в теле функции main() переменные типу int, short int, unsigned int, char, bool, float инициализируются, а переменные типу long int, long long, double, как и положено, не инициализируются?


Множество возможных неинициализированных значений включает в себя 0. Инициализации нет в обоих случаях, просто в релизе объём фрейма функции меньше и переменные переиспользуют одинаковое пространство, в котором оказались нули.

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

man UB

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

PS: 0 != инициализация

RazrFalcon ★★★★★
()

Правильный ответ - это, конечно же, UB, однако тут есть интересный момент. Переменная действительно инициализируется:

	pxor	%xmm0, %xmm0
	movl	std::cout, %edi
	call	std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)

Я не большой знаток intel/gcc, однако бывают разные случаи когда неинициализированную пользователем переменную лучше явно инициализировать внутри компилятора.

Если есть знатоки темы, было бы интересно послушать зачем это было сделано в данном случае.

alexanius ★★
()

clang ничего не зануляет, он даже память не читает, сразу печатает мусор из регистров.

$ clang++ main.cpp && ./a.out 
    main()
int	i = 205242368
double	d = 6.95321e-310
unsign.	i2 = 32767
l int	li = 140734594562328
    func1()
int	a = 32767
bool	b = 0

$ clang++ -O3 main.cpp && ./a.out 
    main()
int	i = 5379584
double	d = 2.64619e-260
unsign.	i2 = 5379584
l int	li = 5379584
    func1()
int	a = 5379584
bool	b = 0

pftBest ★★★★
()

код, в котором используются неинициализированные переменные:
почему переменные сами инициализируются при включении оптимизации?

Потому что могут. Частный случай UB. Остальные спекуляции неуместны, это не относится к языку, а к конкретной ревизии конкретного компилятора, где данное поведение даже может быть ошибочно

Deleted
()

Выходит что при включении оптимизации, вообще, все переменные инициализируются(?).

Нет. Есть такое понятие «неопределённое поведение». UB для своих.

Смысл прост: это место в языковом стандарте, где определено, что поведение не определено, и только *это* определено.

Советую компилировать с ключами -Wall и -Wextra. Выдаёт много информации для размышления. Плюс, есть санитайзер неопределённого поведения.

Macil ★★★★★
()

Gcc ничего не инициализирует, просто так получается что верхняя часть стека при заходе в main заполнена нулями, попробуйте ее немного «помешать» будет совсем другой результат (в дебаге):

#include <iostream>
#include <string.h>

void func1();
void rand_stack();
int main2();


int main()
{
        rand_stack();
        return main2();
}

int main2()
{
    std::cout << "    main()" << std::endl;;

    int i;
    std::cout << "int\ti = " << i << std::endl;
    
    double d;
    std::cout << "double\td = " << d << std::endl;
    
    unsigned i2;
    std::cout << "unsign.\ti2 = " << i2 << std::endl;
    long int li;
    std::cout << "l int\tli = " << li << std::endl;

    func1();
    return 0;
}

void func1()
{
    std::cout << "    func1()" << std::endl;;
    int a;
    std::cout << "int\ta = " << a << std::endl;
    bool b;
    std::cout << "bool\tb = " << b << std::endl;
}

void rand_stack()
{
        char buf[128];
        memset(buf, 0x1E, sizeof(buf));
}

В релизе же (после оптимизации) компилятор «выбрасывает» все ваши переменные и заменяет их на костанты (и выбирает в качестве значения константы 0), для того чтобы переменные не выбрасывались можно их разименовать в коде:

#include <iostream>
#include <string.h>
#include <stdio.h>

void __attribute__ ((noinline)) func1();
void __attribute__ ((noinline)) rand_stack();
int __attribute__ ((noinline)) main2();

void __attribute__ ((noinline)) show_int(const char *prefix, int &v)
{
        std::cout << "int " << prefix << " = " << v << std::endl;
}
void __attribute__ ((noinline)) show_double(const char *prefix, double &v)
{
        std::cout << "double " << prefix << " = " << v << std::endl;
}
void __attribute__ ((noinline)) show_unsigned(const char *prefix, unsigned &v)
{
        std::cout << "unsigned " << prefix << " = " << v << std::endl;
}

int main()
{
        rand_stack();
        return main2();
}

int main2()
{
    int i;
    double d;
    unsigned i2;

    std::cout << "    main()" << std::endl;

    show_int("i", i);
    show_double("d", d);
        show_unsigned("i2", i2);

    func1();
    return 0;
}

void func1()
{
    std::cout << "    func1()" << std::endl;;
    int a;
    show_int("a", a);
    std::cout << "v2: int\ta = " << a << std::endl;

    unsigned b;
    show_unsigned("b", b);
    std::cout << "v2: unsigned\tb = " << b << std::endl;
}

void rand_stack()
{
        char buf[128];
        memset(buf, 0x57, sizeof(buf));
}

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

В релизе же (после оптимизации) компилятор «выбрасывает» все ваши переменные и заменяет их на костанты (и выбирает в качестве значения константы 0)

После оптимизации, компилятор вправе выбросить и этот код:

void rand_stack()
{
        char buf[128];
        memset(buf, 0x1E, sizeof(buf));
}

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