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&’}
    • 5. 与enable_shared_from_this/shared_from_this有关的异常:bad_weak_ptr
    • 6. 报错“没有指定的类型匹配的重载函数”
  • socket编程
    • 1. Bad file number, errno=9

本文汇总一些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(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(new UserInfo(UserInfo::defaultAnonymousUsername(), password, permission));

// 2) 去掉new构造UserInfo,直接提供UserInfo构造函数参数即可
anonymousUserInfo_ = std::make_shared(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, std::allocator > 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, std::allocator > 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

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

  1. 构造函数中调用shared_from_this(),此时实例尚未构造完成,_M_weak_this尚未设置。
#include 
#include 

using namespace std;

class D : public std::enable_shared_from_this
{
public:
	D() {
		cout << "D::D()" << endl;

		// 这里会throw std::exception异常
		shared_ptr p = shared_from_this();
	}
};

/**
 * 导致抛出异常bad_weak_ptr的示例演示
 */
int main()
{
	shared_ptr 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 
#include 

using namespace std;

class D : public enable_shared_from_this
{
public:
	D() {
		cout << "D::D()" << endl;
	}

	void func() {
		cout << "D::func()" << endl;
		shared_ptr 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 
#include 

using namespace std;

class D : public enable_shared_from_this
{
public:
	D() {
		cout << "D::D()" << endl;
	}

	void func() {
		cout << "D::func()" << endl;
		shared_ptr 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(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(new D);

具体发生在这段调用链中:

      template>
	explicit
	shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }


      template>
	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::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
	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
	friend class __shared_ptr;
}

而最开始引发异常问题auto me = shared_from_this();,也是跟这种情形相同,解决办法是将A中直接将D d作为成员,修改为将shared_ptr d作为成员,这样的话,就能将D类对象用shared_ptr包裹起来了。

最后,给出shared_from_this()的正确用法。关键点:

  1. 不要在构造函数中调用shared_from_this;
  2. 用shared_ptr包裹要使用shared_from_this的类对象,该对象只能是new出来的,不能存放在栈上。
// OK,shared_from_this正确用法
#include 
#include 

using namespace std;

class D : public enable_shared_from_this
{
public:
	D() {
		cout << "D::D()" << endl;
	}

	void func() {
		cout << "D::func()" << endl;
		shared_ptr p = shared_from_this();
	}
};

int main()
{
	shared_ptr p(new D);
	p->func();
	return 0;
}

6. 报错“没有指定的类型匹配的重载函数”

详细英文报错信息:

no declaration matches ‘ftp::DataConnection::DataConnection(std::shared_ptr, 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 session, 
		muduo::net::EventLoop* loop,
		TransferMode mode,
		const muduo::net::InetAddress& serverAddr, 
		const std::string& nameArg = std::string("DataConnection"));
	~DataConnection();
...
};

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 acceptSocket_;
	...
};

DataAcceptor::~DataAcceptor()
{
	...
	if (get_pointer(acceptSocket_)) {
		::close(acceptSocket_->fd()); // 这里又对socket接管的fd进行了一次close
	}
	...
}

解决办法:去掉DataAcceptor析构函数中,主动close sockfd的操作。