Хотя программа на C++ компилируется без ошибок во время выполнения, я использую std::bad_alloc

#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

Итак, вам нужно что-то вроде std::format ? Или библиотека, на которой основан стандарт?

Some programmer dude 01.09.2024 20:11

Не используйте имена, начинающиеся с подчеркивания. Они зарезервированы для использования компилятором. В частности, я ожидаю, что _[standard library name] будет использоваться компилятором для какой-то внутренней реализации.

Yksisarvinen 01.09.2024 20:12

У вас есть _sprintf() возврат лямбды, которая фиксирует локальные переменные стека по ссылке, а затем вызывает их позже, по истечении времени жизни кадра стека. Добро пожаловать в C++ и неопределенное поведение.

marko 01.09.2024 20:17

Python — это язык со сборкой мусора, а C++ — нет. В C++ сохранение ссылки на что-либо не сохраняет ее работоспособность. Изменение [&] на [=] позволит избежать этой проблемы, но я не знаю, возникнут ли при этом другие проблемы в вашем коде.

john 01.09.2024 20:57

Марко и @john дали мне решение. Если изменить [&] на [=], программа запустится успешно. Спасибо. Если вы хотите, напишите свой ответ отдельно, а не в разделе комментариев, чтобы получить оценку принятого ответа.

Dragno 02.09.2024 17:09

Временное prefix + a._s, созданное в операторе return _sprintf(prefix + a._s,a._f);, уничтожается в конце оператора ;. Захват по ссылке сохраняется после временной переменной. Когда уже разрушенное временное значение позже разыменовывается, возникает веселье.

Eljay 02.09.2024 18:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Тот факт, что ваша программа компилируется, не означает, что у вас не может быть логических ошибок. В этом случае у вас ошибки памяти. Ошибки памяти в 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, до сих пор.

Другие вопросы по теме