How to correctly exit from a program, which uses Boost Log?

北城余情 提交于 2020-01-06 05:30:19

问题


The sample program below works fine, but the valgrind shows, that 520 bytes in 6 blocks are still reachable after exit.

#include <iostream>
#include <string>

#include <boost/log/common.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/utility/setup/file.hpp>

#include <boost/phoenix/bind/bind_member_function.hpp>
#include <boost/shared_ptr.hpp>

namespace bl = boost::log;
namespace bp = boost::phoenix;

enum class MySeverityLevel
{
  panic,
  alert,
  critical,
  error,
  warning,
  notice,
  info,
  debug
};

BOOST_LOG_ATTRIBUTE_KEYWORD(Severity, "Severity", MySeverityLevel)
BOOST_LOG_ATTRIBUTE_KEYWORD(Channel, "Channel", std::string)

class Logger
{

public:

  Logger(const std::string& CH, const MySeverityLevel SEV):
  m_log(bl::keywords::channel = CH), m_channel(CH), m_severityMax(SEV)
  {
    m_sink = bl::add_file_log
    (
      bl::keywords::file_name = CH + ".log",
      bl::keywords::filter = bp::bind(&Logger::filter, this, Channel.or_none(), Severity.or_none())
    );
  }

  ~Logger()
  {
    this->terminate();
  }

  void terminate()
  {
    if (m_sink)
    {
      bl::core::get()->remove_sink(m_sink);
      m_sink->flush();
      m_sink.reset();
    }
  }

  void panic(const std::string& S)
  {
    BOOST_LOG_SEV(m_log, MySeverityLevel::panic) << S;
  }

  void debug(const std::string& S)
  {
    BOOST_LOG_SEV(m_log, MySeverityLevel::debug) << S;
  }

private:

  using ChannelValue = bl::value_ref<std::string, tag::Channel>;
  using MySeverityLevelValue = bl::value_ref<MySeverityLevel, tag::Severity>;

  bool filter(const ChannelValue& CH, const MySeverityLevelValue& SEV) const
  {
    if (CH && SEV)
    {
      return (CH.get() == m_channel) && (SEV.get() <= m_severityMax);
    }
    else
    {
      return false;
    }
  }

  bl::sources::severity_channel_logger<MySeverityLevel, std::string> m_log;
  boost::shared_ptr<bl::sinks::sink> m_sink;
  const std::string m_channel;
  const MySeverityLevel m_severityMax;

};

int main()
{
  Logger lgA("A", MySeverityLevel::error);
  lgA.panic("A: Panic");
  lgA.debug("A: Debug");
  lgA.terminate();
}

That's the valgrind output:

hekto@ubuntu:~$ valgrind --leak-check=full --show-leak-kinds=all ./tc0013
==48934== Memcheck, a memory error detector
==48934== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==48934== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==48934== Command: ./tc0013
==48934== 
==48934== 
==48934== HEAP SUMMARY:
==48934==     in use at exit: 520 bytes in 6 blocks
==48934==   total heap usage: 83 allocs, 77 frees, 97,276 bytes allocated
==48934== 
==48934== 8 bytes in 1 blocks are still reachable in loss record 1 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x4E98C61: boost::log::v2_mt_posix::sources::aux::get_severity_level() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x11723B: boost::log::v2_mt_posix::sources::aux::severity_level<MySeverityLevel>::set_value(MySeverityLevel) (severity_feature.hpp:135)
==48934==    by 0x115E74: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:252)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== 8 bytes in 1 blocks are still reachable in loss record 2 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x4E9EF69: boost::log::v2_mt_posix::aux::this_thread::get_id() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x4E8BB96: boost::log::v2_mt_posix::core::open_record(boost::log::v2_mt_posix::attribute_set const&) (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x118DEA: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:259)
==48934==    by 0x11825B: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::open_record_with_channel_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&, boost::parameter::void_) (channel_feature.hpp:195)
==48934==    by 0x1172B2: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (channel_feature.hpp:171)
==48934==    by 0x115E8B: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:253)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== 16 bytes in 1 blocks are still reachable in loss record 3 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x512A5BA: boost::detail::add_thread_exit_function(boost::detail::thread_exit_function_base*) (in /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.65.1)
==48934==    by 0x4E98C9A: boost::log::v2_mt_posix::sources::aux::get_severity_level() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x11723B: boost::log::v2_mt_posix::sources::aux::severity_level<MySeverityLevel>::set_value(MySeverityLevel) (severity_feature.hpp:135)
==48934==    by 0x115E74: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:252)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== 24 bytes in 1 blocks are still reachable in loss record 4 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x4E98C84: boost::log::v2_mt_posix::sources::aux::get_severity_level() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x11723B: boost::log::v2_mt_posix::sources::aux::severity_level<MySeverityLevel>::set_value(MySeverityLevel) (severity_feature.hpp:135)
==48934==    by 0x115E74: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:252)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== 24 bytes in 1 blocks are still reachable in loss record 5 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x512A1BC: boost::detail::make_external_thread_data() (in /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.65.1)
==48934==    by 0x512A5AD: boost::detail::add_thread_exit_function(boost::detail::thread_exit_function_base*) (in /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.65.1)
==48934==    by 0x4E98C9A: boost::log::v2_mt_posix::sources::aux::get_severity_level() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x11723B: boost::log::v2_mt_posix::sources::aux::severity_level<MySeverityLevel>::set_value(MySeverityLevel) (severity_feature.hpp:135)
==48934==    by 0x115E74: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:252)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== 440 bytes in 1 blocks are still reachable in loss record 6 of 6
==48934==    at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==48934==    by 0x512A03D: boost::detail::make_external_thread_data() (in /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.65.1)
==48934==    by 0x512A5AD: boost::detail::add_thread_exit_function(boost::detail::thread_exit_function_base*) (in /usr/lib/x86_64-linux-gnu/libboost_thread.so.1.65.1)
==48934==    by 0x4E98C9A: boost::log::v2_mt_posix::sources::aux::get_severity_level() (in /usr/lib/x86_64-linux-gnu/libboost_log.so.1.65.1)
==48934==    by 0x11723B: boost::log::v2_mt_posix::sources::aux::severity_level<MySeverityLevel>::set_value(MySeverityLevel) (severity_feature.hpp:135)
==48934==    by 0x115E74: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_severity_logger<boost::log::v2_mt_posix::sources::basic_channel_logger<boost::log::v2_mt_posix::sources::basic_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, MySeverityLevel>::open_record_unlocked<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (severity_feature.hpp:252)
==48934==    by 0x114961: boost::log::v2_mt_posix::record boost::log::v2_mt_posix::sources::basic_composite_logger<char, boost::log::v2_mt_posix::sources::severity_channel_logger<MySeverityLevel, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::log::v2_mt_posix::sources::single_thread_model, boost::log::v2_mt_posix::sources::features<boost::log::v2_mt_posix::sources::severity<MySeverityLevel>, boost::log::v2_mt_posix::sources::channel<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::open_record<boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> >(boost::parameter::aux::tagged_argument<boost::log::v2_mt_posix::keywords::tag::severity, MySeverityLevel const> const&) (basic_logger.hpp:575)
==48934==    by 0x1135A5: Logger::panic(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (tc0013.cpp:81)
==48934==    by 0x11097B: main (tc0013.cpp:120)
==48934== 
==48934== LEAK SUMMARY:
==48934==    definitely lost: 0 bytes in 0 blocks
==48934==    indirectly lost: 0 bytes in 0 blocks
==48934==      possibly lost: 0 bytes in 0 blocks
==48934==    still reachable: 520 bytes in 6 blocks
==48934==         suppressed: 0 bytes in 0 blocks
==48934== 
==48934== For counts of detected and suppressed errors, rerun with: -v 
==48934== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

In my real application the Logger class lives in a library, that's why I need to properly terminate it (the termination is performed by the systemctl). However, I still see exceptions in syslog during the exit from my application:

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::log::v2_mt_posix::system_error> >'

I think that finding a way to get rid of these "520 bytes in 6 blocks" after exit will help me to terminate my application clearly.

How to write the terminate function (see above) correctly - in order not to leave anything in Boost Log internals reachable?

  • OS: Ubuntu 18.04.1 LTS
  • Compiler: g++ 7.3.0
  • Boost: 1.65.1
  • Valgrind: 3.13.0

回答1:


tl;dr; boost.log is hopelessly doomed, use something else...

The peculiar detail in these call stacks is repeating invocation of boost::detail::add_thread_exit_function which is implementation of boost::this_thread::at_thread_exit that is obviously used to perform some cleanup actions when thread is about to end. However this function can not be used with system-specific thread ending APIs and functions such as exit. As stated in the docs:

In particular, returning from main() is equivalent to call to exit(), so will not call any functions registered with at_thread_exit()

So boost.log does not perform any cleanup when being invoked from main thread. Moreover, (among other ugly things) boost.log uses Meyer's singleton to hold an instance of core context, so library users have no control over lifetime of core context (and objects owned by it) and are prone to static [un]initialization order fiasco.



来源:https://stackoverflow.com/questions/54016819/how-to-correctly-exit-from-a-program-which-uses-boost-log

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!