#include <iostream>
#include <string>
template<class F>
struct I
{
F _f;
I(F f):_f(f){}
};
template<class F>
struct C
{
F _f;
C(F f):_f(f){}
};
template<class F>
struct S
{
F _f;
std::string _s;
S(std::string const& s,F f):_s(s),_f(f)
{
}
};
C c(std::string("."));
S s1(std::string(",Char: "),c);
I i(s1);
S s2(std::string("Int: "),i);
std::string _sprintf(std::string const& prefix,std::string const& str)
{
return prefix+str;
}
template<class A>
auto _sprintf(std::string const& prefix,I<A> const& a)
{
return [&](int i){ return _sprintf(prefix+std::to_string(i),a._f);};
}
template<class A>
auto _sprintf(std::string const& prefix,C<A> const& a)
{
return [&](char c){return _sprintf(prefix + std::string(1,c),a._f);};
}
template<class A>
auto _sprintf(std::string const& prefix,S<A> const& a)
{
return _sprintf(prefix + a._s,a._f);
}
template<class Fmt>
auto sprintf(Fmt const& fmt)
{
return _sprintf("",fmt);
}
int main()
{
auto test = sprintf(s2);
std::cout<<test(5)('x');
}
Я пытаюсь создать типобезопасный sprintf. Я взял пример из статьи о Haskell и попытался преобразовать его в C++. Компилятор не жалуется, но во время выполнения я беру завершить вызов после создания экземпляра 'std::bad_alloc' что(): std::bad_alloc zsh: инструкция IOT (дамп ядра)./printf
Не используйте имена, начинающиеся с подчеркивания. Они зарезервированы для использования компилятором. В частности, я ожидаю, что _[standard library name]
будет использоваться компилятором для какой-то внутренней реализации.
У вас есть _sprintf()
возврат лямбды, которая фиксирует локальные переменные стека по ссылке, а затем вызывает их позже, по истечении времени жизни кадра стека. Добро пожаловать в C++ и неопределенное поведение.
Python — это язык со сборкой мусора, а C++ — нет. В C++ сохранение ссылки на что-либо не сохраняет ее работоспособность. Изменение [&]
на [=]
позволит избежать этой проблемы, но я не знаю, возникнут ли при этом другие проблемы в вашем коде.
Марко и @john дали мне решение. Если изменить [&] на [=], программа запустится успешно. Спасибо. Если вы хотите, напишите свой ответ отдельно, а не в разделе комментариев, чтобы получить оценку принятого ответа.
Временное prefix + a._s
, созданное в операторе return _sprintf(prefix + a._s,a._f);
, уничтожается в конце оператора ;
. Захват по ссылке сохраняется после временной переменной. Когда уже разрушенное временное значение позже разыменовывается, возникает веселье.
Тот факт, что ваша программа компилируется, не означает, что у вас не может быть логических ошибок. В этом случае у вас ошибки памяти. Ошибки памяти в C++ могут проявляться далеко от их источника. Такие инструменты, как очиститель адресов, могут помочь обнаружить проблемы с памятью в их источнике. В этом случае дезинфицирующее средство адреса сообщает следующее:
=================================================================
==1==ERROR: AddressSanitizer: stack-use-after-return on address 0x71e077b000b0 at pc 0x71e07a0794b6 bp 0x7ffc765a61f0 sp 0x7ffc765a59b0
READ of size 5 at 0x71e077b000b0 thread T0
#0 0x71e07a0794b5 in memcpy (/opt/compiler-explorer/gcc-14.2.0/lib64/libasan.so.8+0xfa4b5) (BuildId: e522418529ce977df366519db3d02a8fbdfe4494)
#1 0x71e079e6b8c1 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) (/opt/compiler-explorer/gcc-14.2.0/lib64/libstdc++.so.6+0x15e8c1) (BuildId: 998334304023149e8c44e633d4a2c69800a2eb79)
#2 0x4042b2 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&) /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/basic_string.h:3691
#3 0x4030f0 in _sprintf<S<C<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, I<S<C<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > const&)::{lambda(int)#1}::operator()(int) const /app/example.cpp:42
#4 0x40251c in main /app/example.cpp:65
#5 0x71e079829d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#6 0x71e079829e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#7 0x402334 in _start (/app/output.s+0x402334) (BuildId: 1fd827987fb5ef5739ae395faa6d440683767fc7)
https://godbolt.org/z/EE8sd5cqK
Использование стека после возврата обычно представляет собой висячую проблему с ссылками. Вызов _sprintf("",fmt);
создает временный std::string
, который привязывается к параметру std::string const& prefix
. prefix
захватывается по ссылке, но она временно умирает в конце sprintf
, оставляя возвращенную лямбду с висячей ссылкой. Чтобы решить эту проблему, вы можете захватить по значению:
template<class A>
auto _sprintf(std::string const& prefix,I<A> const& a)
{
return [prefix, &a](int i){ return _sprintf(prefix+std::to_string(i),a._f);};
}
template<class A>
auto _sprintf(std::string const& prefix,C<A> const& a)
{
return [prefix, &a](char c){return _sprintf(prefix + std::string(1,c),a._f);};
}
В качестве примечания: как правило, вы можете и должны избегать const std::string&
в C+ и вместо этого использовать std::string_view
. Подобный пожизненный уход необходим и std::string_view
, до сих пор.
Итак, вам нужно что-то вроде std::format ? Или библиотека, на которой основан стандарт?