C++ 编译、调试错误总结
- C/C++编程
- 1. extra qualification on member
- 2. no matching function for call to
- 3. undefined reference to
ftp::Filesystem::cleanPathNative
- 4. cannot convert ‘std::_Bind_helper’ ... to ‘const CloseCallback&’ {aka ‘const std::function<void(const std::shared_ptrmuduo::net::TcpConnection&)>&’}
- 5. 与enable_shared_from_this/shared_from_this有关的异常:bad_weak_ptr
- 6. 报错“没有指定的类型匹配的重载函数”
- 7. request for member ‘push_back’ , which is of non-class type ‘int’
- 8. warning: ... will be initialized after [-Wreorder]
- 9. Unterminated conditional directive
- 10. Cannot define or redeclare
- 11. Call to non-static member function without an object argument
- socket编程
本文汇总一些C/C++工程实践中碰到的编译、调试问题,以便以后碰到类似问题时,能快速定位、解决。
会长期更新。
C/C++编程
1. extra qualification on member
编译时报错,详细报错信息
extra qualification ‘ftp::UserInfo::’ on member ‘defaultAnonymousUsername’ [-fpermissive]
WSL-GCC-Debug ../../ftpserver/action/../UserInfo.h
对应代码:
...
struct UserInfo
{
public:
std::string username_;
std::string password_;
Permission permission_;
public:
UserInfo(const std::string& username, const std::string& password, Permission permission)
: username_(username),
password_(password),
permission_(permission)
{ }
static const std::string UserInfo::defaultAnonymousUsername()
{
return "anonymous";
}
};
原因:成员函数defaultAnonymousUsername()定义在类的声明中,无需加上类名作为前缀。
解决办法:去掉类声明中定义的UserInfo::defaultAnonymousUsername()
中类名UserInfo::
2. no matching function for call to
编译出错,详细错误信息:
no matching function for call to ‘ftp::UserInfo::UserInfo(ftp::UserInfo*)’
WSL-GCC-Debug /usr/include/c++/9/ext/new_allocator.h
但并不会定位到具体的出错行,只能一个个搜索类名,特别是有构造发生的地方。
注意到提示发生了UserInfo*构造另一个UserInfo,因此代码中找所有可能存在UserInfo构造的地方。
搜索发现,可能的错误代码
bool UserDao::addUser(const std::string& username, const std::string& password, Permission permission)
{
bool res = false;
MutexLockGuard lock(mutex_);
if (isAnonymousUser(username)) { // anonymous user
if (anonymousUserInfo_) { // exist
LOG_ERROR << "Fail to add user " << username << " as \"anonymous\": the anonymous user is already existing.";
res = false;
}
else { // not exist
anonymousUserInfo_ = std::make_shared<UserInfo>(new UserInfo(UserInfo::defaultAnonymousUsername(), password, permission));
LOG_DEBUG << "Successfully add user \"" << username << "\".";
res = true;
}
}
...
}
这里很隐晦的一个错误,就是把std::make_shared误用。std::make_shared本身只需要接受几个待构造对象UserInfo构造函数需要的参数即可,而不是直接接管new UserInfo。因为std::make_shared会根据提供的参数,再构造一个UserInfo对象,而提供的参数是UserInfo,因此才会报错找不到匹配函数UserInfo(UserInfo)。直接接管new出来对象的是std::shared_ptr。
解决办法:
1)将代码中的std::make_shared改为std::shared_ptr;
2)直接为std::make_shared提供构造UserInfo对象需要的参数,而不用new。
// 1) 将std::make_shared改为std::shared_ptr
anonymousUserInfo_ = std::shared_ptr<UserInfo>(new UserInfo(UserInfo::defaultAnonymousUsername(), password, permission));
// 2) 去掉new构造UserInfo,直接提供UserInfo构造函数参数即可
anonymousUserInfo_ = std::make_shared<UserInfo>(UserInfo::defaultAnonymousUsername(), password, permission);
3. undefined reference to ftp::Filesystem::cleanPathNative
编译错误详细信息:
FAILED: bin/ftpserver
: && /usr/bin/c++ -g -DCHECK_PTHREAD_RETURN_VALUE -D_FILE_OFFSET_BITS=64 -Wextra -Werror -Wconversion -Wno-unused-parameter -Wold-style-cast -Woverloaded-virtual -Wpointer-arith -Wshadow -Wwrite-strings -march=native -std=c++11 -rdynamic -O0 -rdynamic ftpserver/CMakeFiles/ftpserver.dir/Configurer.cpp.o ftpserver/CMakeFiles/ftpserver.dir/Filesystem.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpContext.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpDataClient.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpDataServer.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpDir.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpRequest.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpResponse.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpServer.cpp.o ftpserver/CMakeFiles/ftpserver.dir/FtpSession.cpp.o ftpserver/CMakeFiles/ftpserver.dir/IdleConectionEntry.cpp.o ftpserver/CMakeFiles/ftpserver.dir/UserDao.cpp.o ftpserver/CMakeFiles/ftpserver.dir/UserInfo.cpp.o ftpserver/CMakeFiles/ftpserver.dir/main.cpp.o -o bin/ftpserver lib/libmuduo_net.a lib/libmuduo_base.a -lpthread -lrt && :
/usr/bin/ld: ftpserver/CMakeFiles/ftpserver.dir/FtpSession.cpp.o: in function `ftp::FtpSession::toLocalPath(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
/mnt/f/workspace/visual_studio_2022/eftp/build/WSL-GCC-Debug/../../ftpserver/FtpSession.cpp:437: undefined reference to `ftp::Filesystem::cleanPathNative(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
注意到:undefined reference to
ftp::Filesystem::cleanPathNative`,推测可能是没有定义函数。于是找到cleanPathNative函数定义:
...
using namespace ftp;
using namespace ftp::Filesystem;
std::string cleanPathNative(const std::string& path)
{
return cleanPath(path, '/');
}
注意到cleanPathNative是ftp::Filesystem命名空间内的函数,但定义并没有包裹在命名空间内,会被当成全局函数。而客户使用ftp::Filesystem::cleanPathNative,就会无法链接。
改正方法:为cleanPathNative定义加上命名空间。
std::string Filesystem::cleanPathNative(const std::string& path)
{
return cleanPath(path, '/');
}
4. cannot convert ‘std::_Bind_helper’ ... to ‘const CloseCallback&’ {aka ‘const std::function<void(const std::shared_ptrmuduo::net::TcpConnection&)>&’}
出现莫名其妙的错误,详细报错信息:
cannot convert ‘std::_Bind_helper<false, void (ftp::DataConnection::*)(const int&), ftp::DataConnection*, const std::_Placeholder<1>&>::type’ {aka ‘std::_Bind<void (ftp::DataConnection::*(ftp::DataConnection*, std::_Placeholder<1>))(const int&)>’} to ‘const CloseCallback&’ {aka ‘const std::function<void(const std::shared_ptr<muduo::net::TcpConnection>&)>&’} WSL-GCC-Debug F:\workspace\visual_studio_2022\eftp\ftpserver\DataConnection.cpp 63
定位到报错的源码,发现是这一行:
// DataConnection.cpp
void DataConnection::newConnection(int sockfd, const InetAddress& peerAddr)
{
...
conn->setCloseCallback( // Set close callback
std::bind(&DataConnection::removeConnection, this, _1)); // FIXME: unsafe
}
很奇怪,并没有发现这一行有什么问题。因为conn->setCloseCallback接受类型为const CloseCallback&
的参数,而CloseCallback定义为:
typedef std::function<void (const TcpConnectionPtr&)> CloseCallback;
可以知道,CloseCallback的定义跟DataConnection::removeConnection函数声明时一样的:
// DataConnection.cpp
// DataConnection::removeConnection 定义
void DataConnection::removeConnection(const TcpConnectionPtr& conn)
{
loop_->runInLoop(
std::bind(&DataConnection::removeConnectionInLoop, this, conn));
}
接着看下面一个错误提示信息:
no declaration matches ‘void ftp::DataConnection::removeConnection(const TcpConnectionPtr&)’ WSL-GCC-Debug F:\workspace\visual_studio_2022\eftp\ftpserver\DataConnection.cpp 67
发现居然找不到匹配DataConnection::removeConnection定义的函数。找到头文件源码,查看之:
// DataConnection.h
#include "muduo/base/noncopyable.h"
#include "muduo/net/TcpConnection.h"
...
class DataConnection : muduo::noncopyable
{
public:
...
void removeConnection(const TcpConnectionPtr& conn);
};
表面看似乎没什么问题,但TcpConnectionPtr是什么类型呢?
在.h文件中,并没有引入对应的命名空间。修正方法:
// 修正后,添加命名空间
void removeConnection(const muduo::net::TcpConnectionPtr& conn);
5. 与enable_shared_from_this/shared_from_this有关的异常:bad_weak_ptr
线程池的子线程运行函数捕捉到std::exception& ex异常,异常信息(ex.what()):bad_weak_ptr。Debug的时候,可以定位到是下面catch语句捕捉到异常了,然后调用abort终止进程。
/**
* set Thread name, tid
*
* run thread func set by ctor
*/
void runInThread()
{
*tid_ = muduo::CurrentThread::tid(); // help to cache current thread tid
tid_ = NULL;
latch_->countDown();
latch_ = NULL; // as latch_'s member count_ init value = 1, abandon it after countDown()
muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str();
// Set the name of the calling thread
::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName);
try {
func_();
muduo::CurrentThread::t_threadName = "finished";
}
...
catch (const std::exception& ex)
{
muduo::CurrentThread::t_threadName = "crashed";
fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
fprintf(stderr, "reason: %s\n", ex.what()); // 这里打印异常信息:reason: bad_weak_ptr
abort();
}
...
}
去掉try-catch语句后,定位到出现异常的地方是
// 与自定义代码无关的部分 第一现场
template<typename _Yp, typename = _Constructible<const weak_ptr<_Yp>&>>
explicit shared_ptr(const weak_ptr<_Yp>& __r)
: __shared_ptr<_Tp>(__r) { }
// 自定义代码部分 第一现场
void FtpSession::handleFtpCommandLIST(const std::string& param)
{
...
auto me = shared_from_this();
}
这是为什么呢?
因为调用shared_from_this()的时候,实际上返回的是基类enable_from_this的weak_ptr成员_M_weak_this。
使用shared_from_this()导致bad_weak_ptr异常的常见原因可能有:
- 构造函数中调用shared_from_this(),此时实例尚未构造完成,_M_weak_this尚未设置。
#include <memory>
#include <iostream>
using namespace std;
class D : public std::enable_shared_from_this<D>
{
public:
D() {
cout << "D::D()" << endl;
// 这里会throw std::exception异常
shared_ptr<D> p = shared_from_this();
}
};
/**
* 导致抛出异常bad_weak_ptr的示例演示
*/
int main()
{
shared_ptr<D> a(new D);
return 0;
}
编译后,运行结果:
D::D()
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
- shared_ptr所指对象存放在栈上,而没有存放到堆中,没有用shared_ptr
方式构造,因此,enable_shared_from_this 中的weak_ptr所指对象也就没有被赋值。这样,在d.func()中调用shared_from_this()时,会导致异常
#include <memory>
#include <iostream>
using namespace std;
class D : public enable_shared_from_this<D>
{
public:
D() {
cout << "D::D()" << endl;
}
void func() {
cout << "D::func()" << endl;
shared_ptr<D> p = shared_from_this();
}
};
int main()
{
D d;
d.func();
return 0;
}
编译、运行结果:
D::D()
D::func()
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
- 一个非常隐晦的错误用法:用类A的成员D继承自enable_shared_from_this
,且使用shared_from_this()。虽然用shared_ptr包裹了A类对象,但并没有包裹D类对象,在对D调用shared_from_this()时,因此,基类成员_M_weak_this并没有被初始化。
#include <memory>
#include <iostream>
using namespace std;
class D : public enable_shared_from_this<D>
{
public:
D() {
cout << "D::D()" << endl;
}
void func() {
cout << "D::func()" << endl;
shared_ptr<D> p = shared_from_this(); // 这里会抛出异常bad_weak_ptr
}
};
class A
{
public:
A() {
cout << "A::A()" << endl;
}
void funcA() {
cout << "A::funcA()" << endl;
d.func();
}
private:
D d;
};
int main()
{
shared_ptr<A> a(new A());
a->funcA();
return 0;
}
注意:new D对象时,基类_M_weak_this会被默认初始化,而当用shared_ptr
也就是说,_M_weak_this真正有意义的初始化发生在构造包裹D类对象的shared_ptr指针时。
shared_ptr<D> d(new D);
具体发生在这段调用链中:
template<typename _Yp, typename = _Constructible<_Yp*>>
explicit
shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }
template<typename _Yp, typename = _SafeConv<_Yp>>
explicit
__shared_ptr(_Yp* __p)
: _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type())
{
static_assert( !is_void<_Yp>::value, "incomplete type" );
static_assert( sizeof(_Yp) > 0, "incomplete type" );
_M_enable_shared_from_this_with(__p);
}
template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept
{
if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
__base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}
_M_weak_assign是class enable_shared_from_this的private成员函数,__shared_ptr是class enable_shared_from_this的友元类,可以通过_M_weak_assign来初始化_M_weak_this(weak_ptr)。
class enable_shared_from_this
{
...
private:
template<typename _Tp1>
void
_M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
{ _M_weak_this._M_assign(__p, __n); }
// Found by ADL when this is an associated class.
friend const enable_shared_from_this*
__enable_shared_from_this_base(const __shared_count<>&,
const enable_shared_from_this* __p)
{ return __p; }
template<typename, _Lock_policy>
friend class __shared_ptr;
}
而最开始引发异常问题auto me = shared_from_this();
,也是跟这种情形相同,解决办法是将A中直接将D d
作为成员,修改为将shared_ptr<D> d
作为成员,这样的话,就能将D类对象用shared_ptr包裹起来了。
最后,给出shared_from_this()的正确用法。关键点:
- 不要在构造函数中调用shared_from_this;
- 用shared_ptr包裹要使用shared_from_this的类对象,该对象只能是new出来的,不能存放在栈上。
// OK,shared_from_this正确用法
#include <memory>
#include <iostream>
using namespace std;
class D : public enable_shared_from_this<D>
{
public:
D() {
cout << "D::D()" << endl;
}
void func() {
cout << "D::func()" << endl;
shared_ptr<D> p = shared_from_this();
}
};
int main()
{
shared_ptr<D> p(new D);
p->func();
return 0;
}
6. 报错“没有指定的类型匹配的重载函数”
详细英文报错信息:
no declaration matches ‘ftp::DataConnection::DataConnection(std::shared_ptr<ftp::FtpSession>, muduo::net::EventLoop*, ftp::TransferMode, const muduo::net::InetAddress&, const string&)’
自定义构造函数DataConnection,虽然在DataConnection.cpp文件中include了包含FtpSession定义的头文件,但在DataConnection.h头文件中,并没有前向声明FtpSession,也没有include其定义文件。这样.cpp文件中找不到与.h对应的构造函数定义。
解决办法(选其中之一即可):
1)在头文件中添加FtpSession前向声明(如果使用智能指针或引用,建议用该方式);
2)直接include FtpSession.h。
// DataConnection.h
class FtpSession; // 添加前向声明
// 或者include FtpSession头文件
// #include "FtpSession.h"
class DataConnection : muduo::noncopyable
{
public:
DataConnection(std::shared_ptr<FtpSession> session,
muduo::net::EventLoop* loop,
TransferMode mode,
const muduo::net::InetAddress& serverAddr,
const std::string& nameArg = std::string("DataConnection"));
~DataConnection();
...
};
7. request for member ‘push_back’ , which is of non-class type ‘int’
完整错误信息:
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 request for member ‘push_back’ in ‘((zlog::Conf*)this)->zlog::Conf::levels_’, which is of non-class type ‘int’ linux-debug F:\workspace\visual_studio_2022\zlog_cpp\src\conf.cc 298
错误代码处:
// conf.cc文件
// Conf class member function
{
auto level = std::make_shared<Level>(line_str.c_str());
levels_.push_back(level);
...
}
levels_类型,定义于conf.h头文件:
class Level;
class Conf
{
...
private:
std::vector<LevelPtr> levels_;
...
}
LevelPtr类型定义位于Level.h:
using LevelPtr = std::shared_ptr<Level>;
原因:头文件中定义levels_时,用到了LevelPtr 类型作为vector元素类型,但conf.h中并未include头文件<level.h>,而是用到前向声明class Level
,也就是说在conf.h文件中,并不知道LevelPtr 是什么类型。 而vector
有三种改正方法:
1)在conf.h头文件中,include <level.h>,而不是class Level前向声明。
2)将vector元素类型由LevelPtr改为std::shared_ptr
3)将LevelPtr的类型定义放到另一个公共文件中如common.h,conf.h头文件include该文件即可。
std::vector<LevelPtr> levels_;
// 修改为
std::vector<std::shared_ptr<Level>> levels_;
8. warning: ... will be initialized after [-Wreorder]
出现编译警告"warning ... will be initialized after [-Wreorder]"
In file included from /mnt/f/workspace/visual_studio_2022/zlogcpp/src/config_file.cc:4:
/mnt/f/workspace/visual_studio_2022/zlogcpp/src/config_file.h: In constructor 鈥�zlog::global_configuration_attributes::global_configuration_attributes()鈥�:
/mnt/f/workspace/visual_studio_2022/zlogcpp/src/config_file.h:64:12: warning: 鈥�zlog::global_configuration_attributes::reload_conf_period鈥� will be initialized after [-Wreorder]
64 | size_t reload_conf_period;
| ^~~~~~~~~~~~~~~~~~
/mnt/f/workspace/visual_studio_2022/zlogcpp/src/config_file.h:61:12: warning: 鈥�size_t zlog::global_configuration_attributes::fsync_period鈥� [-Wreorder]
61 | size_t fsync_period;
| ^~~~~~~~~~~~
/mnt/f/workspace/visual_studio_2022/zlogcpp/src/config_file.cc:33:1: warning: when initialized here [-Wreorder]
33 | global_configuration_attributes::global_configuration_attributes()
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
查看源码:
// .h
struct global_configuration_attributes
{
...
// Writing log times >= fsync_period_, then fsync all log message on RAM to ROM.
// Count by every rule.
// After reload
size_t fsync_period;
// Writing log times >= reload_conf_period_, then reload .conf file
// to update rules. Default: 0(disabled).
size_t reload_conf_period;
global_configuration_attributes();
};
// .cc
global_configuration_attributes::global_configuration_attributes()
:
...
, reload_conf_period(ZLOG_CONF_DEFAULT_RELOAD_CONF_PERIOD)
, fsync_period(ZLOG_CONF_DEFAULT_FSYNC_PERIOD)
{
}
发现成员变量初始化顺序(.cc),与声明顺序(.h)不一致。修复方法:调整.cc中变量初始化顺序,与声明顺序保持一致。
9. Unterminated conditional directive
.h头文件报错“Unterminated conditional directive”,例如:
#ifndef MYFTPD_FS_H
#define MYFTPD_FS_H
...
前面没有任何内容。这种情况,通常是由于没有#endif
或者编写错误导致。
10. Cannot define or redeclare
在.h文件中,声明了类FtpSession的方法(构造函数),但是在.cpp文件中实现时,编译报错,提示:
Cannot define or redeclare 'FtpSession' here because namespace '' does not enclose namespace 'FtpSession'
仔细检查.cpp文件中定义的实现,跟.h中声明的格式没有区别。问题出在哪里?
查看报错代码段,发现最下面有一个"}",这个是匿名namespace的右括号,而.h中,类FtpSession并没有声明在这个namespace中,编译器找不到相同命名空间中的声明式,从而报错。
// cpp文件
namespace {
...
FtpSession::FtpSession(FtpConfig &config_, UniqueSocket commandSocket_)
...
{
}
} // 注意这里的反大括号
修正:将这段代码移出namespace
// cpp文件
namespace {
...
} // 注意这里的反大括号
FtpSession::FtpSession(FtpConfig &config_, UniqueSocket commandSocket_)
...
{
}
11. Call to non-static member function without an object argument
发生了一件十分诡异的事情,handlers的定义在FtpSession::OPTS处报错,提示错误信息“Call to non-static member function without an object argument”。
FtpSession类中static成员handlers定义:
std::vector<std::pair<std::string_view, void (FtpSession::*)(char const *)>> const
FtpSession::handlers =
{
{"ABOR", &FtpSession::ABOR},
{"ALLO", &FtpSession::ALLO},
{"APPE", &FtpSession::APPE},
{"CDUP", &FtpSession::CDUP},
{"CWD", &FtpSession::CWD},
{"DELE", &FtpSession::DELE},
{"FEAT", &FtpSession::FEAT},
{"HELP", &FtpSession::HELP},
{"LIST", &FtpSession::LIST},
{"MDTM", &FtpSession::MDTM},
{"MKD", &FtpSession::MKD},
{"MLSD", &FtpSession::MLSD},
{"MLST", &FtpSession::MLST},
{"MODE", &FtpSession::MODE},
{"NLST", &FtpSession::NLST},
{"NOOP", &FtpSession::NOOP},
{"OPTS" &FtpSession::OPTS},
...
};
查阅相关资料,该错误信息通常发生在static函数中,没有通过对象调用(动态)成员函数。但是FtpSession::OPTS函数本身是non-static的,而且即使将函数内容清空,依然会编译报错。
仔细检查后,发现{"OPTS" &FtpSession::OPTS}
这一项没有加逗号,导致编译器认为FtpSession::OPTS是要调用函数,从而报错。
解决办法,为该项添加逗号。
{"OPTS", &FtpSession::OPTS},
socket编程
1. Bad file number, errno=9
关闭(close)sockfd时,报错:"close Bad file number"。对应打印日志代码:
void sockets::close(int sockfd)
{
if (::close(sockfd) < 0)
{
LOG_SYSERR << "sockets::close";
}
}
这是为何?
原来是因为重复close同一个sockfd。原本sockfd被Socket类接管,而Socket析构时,会自动close接管的fd。
explicit Socket(int sockfd)
: sockfd_(sockfd)
{ }
Socket::~Socket()
{
sockets::close(sockfd_); // 析构函数中自动析构sockfd
}
但是,另外又在一个class的析构函数中,主动close了该sockfd。
class DataAcceptor
{
....
private:
std::unique_ptr<muduo::net::Socket> acceptSocket_;
...
};
DataAcceptor::~DataAcceptor()
{
...
if (get_pointer(acceptSocket_)) {
::close(acceptSocket_->fd()); // 这里又对socket接管的fd进行了一次close
}
...
}
解决办法:去掉DataAcceptor析构函数中,主动close sockfd的操作。