一、问题的原因
也不知道是更新gcc版本的原因,还是由于代码修改包含了特殊头文件,导致在新的环境中YouCompleteMe插件(以后简称为ycm)无法进行智能提示,即使对于最简单基础的vector等容器也识别错误,这个对于这么一个杀手级的应用来说就太不应该了。关键是这样就导致整个开发环境的使用就很不方便,所以抽时间看了这个问题的原因。
从现象上看,对于这个最为简单的文件,会导致ycm的头文件包含出现错误:
tsecer@harry: cat main.cpp
#include <cstdlib>
int main(int argc, const char *argv[])
{
return 0;
}
在vim中打开该文件,通过ycm添加的YcmDiags命令,可以看到错误中有下面信息,直观的说就是cstdlib这个文件包含的stdlib.h文件找不到
1 /include/c++/7/cstdlib|75 col 15 error| 'stdlib.h' file not found
2 /include/c++/7/bits/std_abs.h|38 col 15 error| 'stdlib.h' file not found
3 /include/c++/7/bits/std_abs.h|52 col 11 error| no member named 'abs' in the global namespace
二、出问题的原因
明显的,系统中并不存在一个/include/c++/7/cstdlib文件,但是由于clang是兼容gcc的包含规则的,所以可以通过gcc的头文件包含来确认下该文件的具体位置。
tsecer@harry: gcc -E -H main.cpp 2>&1 | fgrep cstdlib
. /usr/include/c++/7/cstdlib
# 1 "/usr/include/c++/7/cstdlib" 1 3
看该文件的75行
46 #if !_GLIBCXX_HOSTED
47 // The C standard does not require a freestanding implementation to
48 // provide <stdlib.h>. However, the C++ standard does still require
49 // <cstdlib> -- but only the functionality mentioned in
50 // [lib.support.start.term].
51
……
68 } // namespace std
69
70 #else
71
72 // Need to ensure this finds the C library's <stdlib.h> not a libstdc++
73 // wrapper that might already be installed later in the include search path.
74 #define _GLIBCXX_INCLUDE_NEXT_C_HEADERS
75 #include_next <stdlib.h>
76 #undef _GLIBCXX_INCLUDE_NEXT_C_HEADERS
77 #include <bits/std_abs.h>
可以看到,报错的位置是使用了一个#include_next指令,可能主要是这个指令导致了头文件包含的失败。
而这个文件其实就是在/usr/include/文件夹中
tsecer@harry: gcc -E -H main.cpp 2>&1 | fgrep stdlib.h
.. /usr/include/stdlib.h
# 1 "/usr/include/stdlib.h" 1 3 4
三、添加对应的头文件包含
知道了这个问题,通过ycm的YcmDebugInfo命令可以看到使用的配置文件,在默认的配置文件中增加对于该文件的包含,改配置文件默认在
/home/tsecer/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/.ycm_extra_conf.py
中,在
36 # These are the compilation flags that will be used in case there's no
37 # compilation database set (by default, one is not set).
38 # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
39 flags = [
40 '-Wall',
……
}
的最后添加-I/usr/include这个指示命令,但是测试之后发现该修改并没有效果。
四、gcc用户手册中对于头文件包含的说明
其中提到了如果同时制定了-isystem和-I选项,那么-I选项会被忽略( If a standard system include directory, or a directory specified with -isystem, is also specified with -I, the -I option is ignored)。而默认的ycm的flags中的确已经通过-isystem指示了/usr/include文件夹。
-I dir
-iquote dir
-isystem dir
-idirafter dir
Add the directory dir to the list of directories to be searched for header files during preprocessing. If dir begins with =, then the = is replaced by the sysroot prefix; see
--sysroot and -isysroot.
Directories specified with -iquote apply only to the quote form of the directive, "#include "file"". Directories specified with -I, -isystem, or -idirafter apply to lookup for
both the "#include "file"" and "#include <file>" directives.
You can specify any number or combination of these options on the command line to search for header files in several directories. The lookup order is as follows:
1. For the quote form of the include directive, the directory of the current file is searched first.
2. For the quote form of the include directive, the directories specified by -iquote options are searched in left-to-right order, as they appear on the command line.
3. Directories specified with -I options are scanned in left-to-right order.
4. Directories specified with -isystem options are scanned in left-to-right order.
5. Standard system directories are scanned.
6. Directories specified with -idirafter options are scanned in left-to-right order.
You can use -I to override a system header file, substituting your own version, since these directories are searched before the standard system header file directories.
However, you should not use this option to add directories that contain vendor-supplied system header files; use -isystem for that.
The -isystem and -idirafter options also mark the directory as a system directory, so that it gets the same special treatment that is applied to the standard system directories.
If a standard system include directory, or a directory specified with -isystem, is also specified with -I, the -I option is ignored. The directory is still searched but as a
system directory at its normal position in the system include chain. This is to ensure that GCC's procedure to fix buggy system headers and the ordering for the "#include_next"
directive are not inadvertently changed. If you really need to change the search order for system directories, use the -nostdinc and/or -isystem options.
五、gcc代码中对该指示的处理
从注释中看,当遇到includ_next的时候,是从指令所在文件所在目录开始继续搜索。
gcc-4.4.7libcppfiles.c
/* Return the directory from which searching for FNAME should start,
considering the directive TYPE and ANGLE_BRACKETS. If there is
nothing left in the path, returns NULL. */
static struct cpp_dir *
search_path_head (cpp_reader *pfile, const char *fname, int angle_brackets,
enum include_type type)
{
cpp_dir *dir;
_cpp_file *file;
if (IS_ABSOLUTE_PATH (fname))
return &pfile->no_search_path;
/* pfile->buffer is NULL when processing an -include command-line flag. */
file = pfile->buffer == NULL ? pfile->main_file : pfile->buffer->file;
/* For #include_next, skip in the search path past the dir in which
the current file was found, but if it was found via an absolute
path use the normal search logic. */
if (type == IT_INCLUDE_NEXT && file->dir
&& file->dir != &pfile->no_search_path)
dir = file->dir->next;
else if (angle_brackets)
dir = pfile->bracket_include;
else if (type == IT_CMDLINE)
/* -include and -imacros use the #include "" chain with the
preprocessor's cwd prepended. */
return make_cpp_dir (pfile, "./", false);
else if (pfile->quote_ignores_source_dir)
dir = pfile->quote_include;
else
return make_cpp_dir (pfile, dir_name_of_file (file),
pfile->buffer ? pfile->buffer->sysp : 0);
if (dir == NULL)
cpp_error (pfile, CPP_DL_ERROR,
"no include path in which to search for %s", fname);
return dir;
}
六、isystem指示的作用
该功能的文档说明,简单来说,系统头文件通常都不能用严格C兼容的语法书写,所以gcc给系统头文件进行特殊处理。所有不是通过"#warning"指示给出的告警都可以被忽略。
2.8 System Headers
The header files declaring interfaces to the operating system and runtime libraries often cannot be written in strictly conforming C. Therefore, GCC gives code found in system headers special treatment. All warnings, other than those generated by ‘#warning’ (see Diagnostics), are suppressed while GCC is processing a system header. Macros defined in a system header are immune to a few warnings wherever they are expanded. This immunity is granted on an ad-hoc basis, when we find that a warning generates lots of false positives because of code in macros defined in system headers.
Normally, only the headers found in specific directories are considered system headers. These directories are determined when GCC is compiled. There are, however, two ways to make normal headers into system headers:
Header files found in directories added to the search path with the -isystem and -idirafter command-line options are treated as system headers for the purposes of diagnostics.
There is also a directive, #pragma GCC system_header, which tells GCC to consider the rest of the current include file a system header, no matter where it was found. Code that comes before the ‘#pragma’ in the file is not affected. #pragma GCC system_header has no effect in the primary source file.
On some targets, such as RS/6000 AIX, GCC implicitly surrounds all system headers with an ‘extern "C"’ block when compiling as C++.
七、修复
将当前使用的配置文件.ycm_extra_conf.py中的
'-isystem',
'/usr/include',
修改为
'-idirafter',
'/usr/include',
即可修复该问题。
其实通过查看clang的命令行选项可以知道,其实/usr/include默认是被包含的,并且如果不指定的话是在所有包含文件的最后
tsecer@harry: echo | ./clang -x c++ -v -E -
clang version 12.0.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
……
clang -cc1 version 12.0.0 based upon LLVM 12.0.0git default target x86_64-unknown-linux-gnu
ignoring nonexistent directory "/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/x86_64-redhat-linux
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/backward
/usr/local/include
/data/home/harry/study/llvm-project-main/build/lib/clang/12.0.0/include
/usr/include
End of search list.
注意:其中的/usr/include是在整个文件包含路径的最后面。
如果在命令行中添加了-isystem /usr/include命令
tsecer@harry: echo | ./clang -x c++ -v -E -isystem /usr/include -
clang version 12.0.0
Target: x86_64-unknown-linux-gnu
Thread model: posix
……
clang -cc1 version 12.0.0 based upon LLVM 12.0.0git default target x86_64-unknown-linux-gnu
ignoring nonexistent directory "/include"
ignoring duplicate directory "/usr/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/include
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/x86_64-redhat-linux
/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/backward
/usr/local/include
/data/home/harry/study/llvm-project-main/build/lib/clang/12.0.0/include
可以看到/usr/include在整个搜索路径的最前面。这也意味着后面被包含到的,在 /usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7文件夹下的cstdlib文件并不能通过#include_next <stdlib.h>指示包含到在前面的" /usr/include"文件夹下的内容。
根据这个情况,其实在.ycm_extra_conf.py配置文件中直接删掉"-isystem /usr/include"指示即可。