0%

关于“undefined reference to”

不太清楚这个是否算是gcc的问题,总之是遇到了。
Gcc的编译log一般如下

1
2
SomePath/SomeSourceFile|6|undefined reference to `Foo'|
||error: ld returned 1 exit status|

这一次我遇到的是

1
2
putty-src/windows/winnet.c:828: undefined reference to `IN6_IS_ADDR_LOOPBACK'
collect2.exe: error: ld returned 1 exit status

这是在用CodeBlocks编译PuTTY的时候遇到的问题,很扰人。用命令行编译没问题,在Visual Studio里面也没问题,换到CodeBlocks之后这个问题困扰了很长时间。
看编译Log可以知道在PuTTY源码的windows目录下的winnet.c文件828行,有一个未定义的函数IN6_IS_ADDR_LOOPBACK,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef NO_IPV6
if (family == AF_INET6) {
return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr);
} else
#endif
if (family == AF_INET) {
#ifndef NO_IPV6
if (step.ai) {
return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
->sin_addr);
} else
#endif

右击函数IN6_IS_ADDR_LOOPBACK,找到函数定义,在头文件ws2tcpip.h有如下定义:

1
2
3
4
5
······
int IN6_IS_ADDR_LOOPBACK(const struct in6_addr *);
······
WS2TCPIP_INLINE int IN6_IS_ADDR_LOOPBACK(const struct in6_addr *a) { return ((a->s6_words[0]==0) && (a->s6_words[1]==0) && (a->s6_words[2]==0) && (a->s6_words[3]==0) && (a->s6_words[4]==0) && (a->s6_words[5]==0) && (a->s6_words[6]==0) && (a->s6_words[7]==0x0100)); }
······

百度搜索undefined reference to,得到的信息非常杂乱,没有很确定的目标。
为了找到原因所在,准备复现问题。
新建文件main.h,写入一下内容

1
2
3
4
5
6
7
8
9
10
11
#ifndef MAIN_H
#define MAIN_H

int Foo(int ,int );

__CRT_INLINE int Foo(int a,int b)
{
return a+b;
}

#endif //MAIN_H

新建文件main.cpp,写入以下内容:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
int main()
{
printf(Foo(1,2));
return 0;
}

出现提示 *main.c|6|undefined reference to `Foo’| *,成功复现,
尝试定位问题

  • main.c改成main.cpp,提示消失
  • __CRT_INLINE 去掉,提示消失
  • CodeBlocks切换到Release,提示消失

右击__CRT_INLINE ,找到定义,在_mingw.h内找到信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
#ifdef __cplusplus
# define __CRT_INLINE inline
#elif defined(_MSC_VER)
# define __CRT_INLINE __inline
#else
# if ( __MINGW_GNUC_PREREQ(4, 3) && __STDC_VERSION__ >= 199901L) \
|| (defined (__clang__))
# define __CRT_INLINE extern inline __attribute__((__gnu_inline__))
# else
# define __CRT_INLINE extern __inline__
# endif
#endif

可以看到__CRT_INLINE 其实是内联函数,那么看起来问题好像是在头文件内同时定义声明了一个函数带来的问题,但是换用g++又没有出现问题,在MSVC上也没有出现问题,用Makefile也没有出现问题,那么是编译的时候少了参数导致的问题了。
于是对着PuTTY源码的的Makefile一个一个加参数,最后确定是因为少了-O(大写的O),这个参数是用来优化代码的,具体讨论可以查看知乎的帖子GCC中-O1 -O2 -O3 优化的原理是什么?,在@vczh的回答

C++的template就设计成,只要你开了优化就可以把封装带来的中间层去掉的形式。因此STL拼命的抽象,然后你开了O2,这些抽象编译后就“不占地方”了,就跟你直接用C为每个类型精心设计的容器类型一样。当然C++显然方便多了。但是你不开O2,你就能感受到那些抽象带来的效果。
作者:vczh
链接:https://www.zhihu.com/question/27090458/answer/35224898
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这应该也就是将后缀改为cpp之后提示会消失的原因,本来这个参数是在编译Release版的程序的时候使用的,CodeBlocks默认的Debug参数并不包含-O,开启这个参数之后,有可能会因为编译器的优化为debug程序带来其他的问题。
其实这个问题在将函数的声明和定义分开写的时候也不会出现(也就是声明写在SomeSourceFile.h,定义写在SomeSourceFile.c/cpp)内也不会复现。

本篇所对应的undefined reference to 'IN6_IS_ADDR_LOOPBACK'问题应该很少见,常见的undefined reference to并不是因为文章中所说的问题,一些更详细的问题可以阅读“undefined reference to” 问题汇总及解决方法
下载建立好的PuTTY的项目文件可以前往PuTTY,内含CodeBlocks和Visual Studio 2017的项目文件。