ранжированный для цикла переменных приводит к возвращению адреса-ссылки локальной переменной?

// g++ --std=c++17 test.cpp -I /usr/local/include -L /usr/local/lib -lboost_system -Wall -pedantic -Wreturn-type -Wstrict-aliasing -Wreturn-local-addr -fsanitize=address -g
// LD_LIBRARY_PATH=/usr/local/lib ./a.out

#include <iostream>
#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

class A {
public:
fs::path path_;

const fs::path & path() const { return path_; }
fs::path & path() { return path_; }
};

class B {
public:
fs::path root_path_;

A path_2;
A path_3;

const fs::path & operator()() const {
for ( const auto & path : {
path_3.path(),
path_2.path(),
root_path_
}) {
if ( not path.empty() ) {
return path;
}
}
throw std::logic_error{"using default-constructed B"};
}
};

int main(int argc, char **argv) {
B b;
b.root_path_ = "foo/";
b.path_2.path() = "foo/bar";
b.path_3.path() = "foo/baz";

std::cout << b() << '\n';

return 0;
}

С приведенным выше кодом, который, насколько мне известно, является допустимым C ++. Вместо этого при вызове я получаю вывод мусора.

g++ изначально не жалуется, однако адрес Sanitizer делает. g++ наконец жалуется при добавлении -O2, Сгенерированное предупреждение

test.cpp: In member function ‘const boost::filesystem::path& B::operator()() const’:
test.cpp:31:12: warning: function may return address of local variable [-Wreturn-local-addr]
return path;
^~~~
test.cpp:29:3: note: declared here
}) {
^

Обратите внимание, что я использую:

$ cat /etc/fedora-release
Fedora release 25 (Twenty Five)
$ g++ --version
g++ (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1)
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Обратите внимание, что я решил ошибка используя вместо этого указатель.

    const fs::path & operator()() const {
for ( const auto * path : {
&path_3.path(),
&path_2.path(),
&root_path_
}) {
if ( not path->empty() ) {
return *path;
}
}
throw std::logic_error{"using default-constructed B"};
}

Но это оставляет некоторые вопросы в моей голове:

  1. Почему g++ не жаловаться на проблему до -O2 добавлен?
  2. что конкретно в моем коде не определено? Я бы сказал, что это четко определено: B::operator() const это … хорошо … постоянный Это должно означать, что объекты используемый внутри это или местные жители или постоянные участники. Доступ к постоянным членам. Создает локальную переменную const auto & который … должен ссылаться на поле члена const. Что именно заставляет его привязываться к временному?

4

Решение

  1. Компилятор не обязан выдавать диагностику для неопределенного поведения. Если компилятор может обнаружить код, который синтаксически допустим, но приводит к неопределенному поведению, а затем жалуется на это, это просто обледенение на вершине торта. ССЗ -O2 включает дополнительные оптимизации и выполняет дополнительный анализ кода; таким образом, gcc может иногда обнаруживать неопределенное поведение, только если включены оптимизации.

  2. Похоже, что ваша итерация диапазона более временный std::initializer_list, Переменная итерации диапазона является ссылкой в ​​список инициализатора. Таким образом, функция в конечном итоге возвращает ссылку на временный объект, о котором лает gcc, здесь. Поскольку временный объект уничтожается при возврате метода, метод возвращает ссылку на уничтоженный объект. Любое использование этой ссылки содержит неопределенное поведение.

Когда вы преобразуете временный диапазон в список указателей, вы выполняете итерацию по значению, и вы не возвращаете ссылку на временный объект, а скорее разыменовываете значение в диапазоне, который является абсолютно кошерным указателем.

Обратите внимание на следующую рекламу отсюда:

Базовый массив не гарантированно существует после жизни
исходный объект списка инициализатора закончился. Хранение для
std :: initializer_list не указан (то есть он может быть автоматическим,
временная или статическая постоянная память, в зависимости от ситуации).

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

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]