C++ 编译、调试错误总结

本文汇总一些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。

参考这篇文章:bad_weak_ptr的原因 | CSDN

使用shared_from_this()导致bad_weak_ptr异常的常见原因可能有:

  1. 构造函数中调用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
  1. 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
  1. 一个非常隐晦的错误用法:用类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包裹D类对象时,才会将_M_weak_this指向new出来的D对象。
也就是说,_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()的正确用法。关键点:

  1. 不要在构造函数中调用shared_from_this;
  2. 用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容器需要提供T的具体类型,因此出现编译错误。

有三种改正方法:
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的操作。

posted @ 2022-05-29 08:59  明明1109  阅读(5117)  评论(0编辑  收藏  举报