分类 C/C++ 下的文章

在看C4P的时候书上提到,有这么一段代码:

    char ch;
    cin >> ch;
    while(ch != '#')
    {
        ...
        cin >> ch;
    }

如果在循环中cin到一个char类型变量中,会使程序陷入死循环。当时就在想,如果拿不到合法的数据的话,不是应该丢掉非法输入,等待下一个合法输入的到来么。后来在网上查找相关信息后,发现C和C++对于这一异常情况有着不同的处理思路。

下文假设有如下定义:int var;

在C语言中,使用scanf("%d", &var);从输入流读取一个整型数据(浮点型同理)。在这种情况下,如果读取到的数据无法解析为对应类型的有效值的话,scanf的返回值为0,表示读入整型数据失败。在这种情况下,scanf不会修改对应的目标变量,下次scanf调用从当前失败的位置继续开始。

在C++中,使用的语句是cin >> var;。如果读取的数据无法转换为var的类型(这里是int)的话,根据C++规范版本,对var有两种不同的处理方式:在C++11之前,var的值不变;C++11及以后,var置零。同时,从此往后的cinfailbit置1,所有对cin的调用均失败返回,且不修改对应变量值。要解决这种情况,需要调用cin.clear();方法重置cin的异常状态,然后才可以继续正常使用cin

从此可以得知,上述代码在C++11的条件下的确会产生无限循环的问题:第一次调用失败时,ch置零,不等于#,循环继续;而后续由于cin处于fail状态,不再产生任何有效的数据读取,使得ch始终维持为0,进而导致无限循环的产生。可以用如下程序验证:

#include <iostream>

using namespace std;

int i = -1;
char ch = '?';

void cpp()
{
    cin >> i;
    cout << i << endl;

    cin >> ch;
    cout << ch << endl;
}

void c()
{
    scanf("%d", &i);
    printf("%d\n", i);

    scanf("%c", &ch);
    printf("%c\n", ch);
}

int main()
{
    // or cpp() to test the c++-style input
    c();

    return 0;
}

参考:
https://www.zhihu.com/question/54476315

最近在看gem5代码的时候发现里面一大堆神奇操作。由于并没有系统地学过C++,导致看别人代码的时候先是一头问号,好容易弄懂之后又只得拍手称绝。这里介绍一下C++11引入的模板逻辑std::enable_if

enable_if包含于type_traits头文件中,本质上是一个结构体,但是通过借用C++模板的特性实现了逻辑功能,因而我更倾向于把它按照功能称作逻辑,而非按照其在语言中的成员类型将其称为结构体/类型。

enable_if本身不算是语言特性,也不是复杂的软件实现,可以说仅仅只算一种语法糖。其功能大致可以概括为,如果模板类中的第一个参数为true的话,按照第二个参数生成一个有效的数据类型定义;为false的话则产生编译报错。这种功能可以允许在C++模板编程的时候,按照enable_if的条件变量决定所指定的函数或变量是否在指定条件的类中定义。

为了理解enable_if的工作原理,需要先了解C++模板类中bool类型参数的作用。简单来说,可以当作模式匹配的参数使用:

template<bool B>
class A
{
public:
    const bool opt = B;
};

对于类A则有两种可能的实例化情况,分别是A<true>()A<false>()。由此,可以分别为这两种模式下的范型定义不同类型的逻辑和代码:

template<>
class A<true>
{
public:
    const bool opt = true;
};

template<>
class A<false>
{
public:
    const bool opt = false;
};

这个时候访问A<true>().optA<false>().opt,则可以实例化为指定的类,读取到opt的值分别为truefalse

然后回来看enable_if的实现。cppreference上给出的参考实现如下:

template<bool B, class T = void>
struct enable_if {};

template<class T>
struct enable_if<true, T> { typedef T type; };  // 有的实现里用的是`using type = T;`,效果相同

可以看见,enable_if只需要四行代码就可以完成功能,但是如果要求要使用的人在每次使用的时候自己写,又显得太麻烦。可能就是出于代码可读性的目的放进了头文件中吧。

这里可以看见的是,与上文不同的地方在于,这里定义完enable_if<bool, T>的原型之后,只定义了enable_if<true, T>类型,而没有定义enable_if<false, T>类型。这样做的后果是,如果在后续过程中使用到了enable_if<false, T>类型的话,编译器会报出类似invalid use of incomplete type的错误,示意所使用的类型并没有完成显式声明。为了避免这个问题,enable_if巧妙地在类名后添加了一组花括号,示意默认实现是一个没有任何成员的空类。这样就确保了类是完整的,同时避免了类中夹杂不需要的属性。

那么,此时访问enable_if<true, T>::type的话,是可以获取到type所指类型T的,而访问enable_if<false>::type的话,虽然类型是完整的,但如上文所述,会因为访问没有定义的成员类型而在编译阶段被编译器当作错误报出。举一个简单的例子:

#include <type_traits>

...

    std::enable_if<false, void>::type t;

在编译阶段,编译器会指出这一行存在问题,因为在enable_if的首个参数为false的情况下,是没有定义type成员类型的:

error: 'type' is not a member of 'std::enable_if<false, void>'

但是如果放在这个模板逻辑的应用场景中,却又不会报错:

template<bool B>
class A
{
public:
    typename std::enable_if<B, bool>::type isTrue()
    {
        return true;
    }
};

使用这段代码声明模板类AA<true>()A<false>()都可以正常编译通过,但是只有A<true>()可以访问成员方法isTrue()enable_if的目的达到了,但是在实例化A<false>()的时候显然是遇到了typename std::enable_if<false, bool>::type isTrue()这个情况的。编译器并没有报错,而是选择在不实现这个函数的同时,正常完成剩余部分的编译。

编译器的这种行为与C++的一种新兴提出的C++编译思想,SFINAE有着极大的关系。SFINAE是英文短句Substitution Failure Is Not An Error(匹配失败不是(编译)错误)的缩写。SFINAE的一种体现是,在模板类中使用推导类型构建类方法的时候,如果出现了匹配错误(无符合匹配或多重匹配)的话,选择不报错,而是直接放弃对该模板函数的实例化操作。这也正是enable_if能够实现的核心原理:一般编码情况下,访问到enable_if<false, T>::type的话会报出类型没有该成员的错误;而在模板类编程中,由于找不到enable_if<false, T>::type对应的数据类型,根据SFINAE规则,编译器会选择放弃这种情况下对该函数的实现(而不是报错说找不到匹配类型),进而完成后续的编译工作,生成正常可用的程序。

参考:
https://zhuanlan.zhihu.com/p/20029261
https://ouuan.github.io/post/c-11-enable-if-%E7%9A%84%E4%BD%BF%E7%94%A8/
https://en.cppreference.com/w/cpp/types/enable_if
https://en.cppreference.com/w/cpp/language/sfinae

Esper曾说过,一千个科研民工有一千种编译报错

最近从实验室拿到了SPEC软件包用于测试软件性能,本满心欢喜地以为可以用SPEC提供的预编译文件直接跑,结果install.sh直接的当头一棒,说是预编译文件不支持当前环境,要自己从头编译。

然而谁也没想到的是,这只是噩梦的开始。

首先来到tools/src目录下,使用./buildtools命令开始编译工具链。

  1. gnu make的问题。gnu-make一上场就给我们出了一个难题:

    glob/libglob.a(glob.o): In function `glob_in_dir':
    /opt/make-3.81/glob/glob.c:1361: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1336: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1277: undefined reference to `__alloca'
    /opt/make-3.81/glob/glob.c:1250: undefined reference to `__alloca'
    glob/libglob.a(glob.o): In function `glob':
    /opt/make-3.81/glob/glob.c:575: undefined reference to `__alloca'
    glob/libglob.a(glob.o):/opt/make-3.81/glob/glob.c:726: more undefined references to `__alloca' follow
    collect2: error: ld returned 1 exit status
    Makefile:410: recipe for target 'make' failed
    make[2]: *** [make] Error 1
    make[2]: Leaving directory '/opt/make-3.81'
    Makefile:603: recipe for target 'all-recursive' failed
    make[1]: *** [all-recursive] Error 1
    make[1]: Leaving directory '/opt/make-3.81'
    Makefile:326: recipe for target 'all' failed
    make: *** [all] Error 2

    通过查阅资料 [提出问题] [解决问题] 发现,该问题的解决者也没能对问题的产生原因给出明确的解释,好在留下了有效的解决方案: 将make-3.81/glob/glob.c中,第54行的

    # if _GNU_GLOB_INTERFACE_VERSION == GLOB_INTERFACE_VERSION

    改成

    # if _GNU_GLOB_INTERFACE_VERSION >= GLOB_INTERFACE_VERSION

    然后重新编译解决问题。

  2. specmd5sum的问题。
    gcc -DHAVE_CONFIG_H    -I/home/gem5/cpu2006/tools/output/include   -I. -Ilib  -c -o md5sum.o md5sum.c
    In file included from md5sum.c:38:0:
    lib/getline.h:31:1: error: conflicting types for 'getline'
    getline PARAMS ((char **_lineptr, size_t *_n, FILE *_stream));
    ^
    In file included from md5sum.c:26:0:
    /usr/include/stdio.h:678:20: note: previous declaration of 'getline' was here
    extern _IO_ssize_t getline (char **__restrict __lineptr,
                    ^
    In file included from md5sum.c:38:0:
    lib/getline.h:34:1: error: conflicting types for 'getdelim'
    getdelim PARAMS ((char **_lineptr, size_t *_n, int _delimiter, FILE *_stream));
    ^
    In file included from md5sum.c:26:0:
    /usr/include/stdio.h:668:20: note: previous declaration of 'getdelim' was here
    extern _IO_ssize_t getdelim (char **__restrict __lineptr,
                     ^
    make: *** [md5sum.o] Error 1
    + testordie 'error building specmd5sum'
    + test 2 -ne 0
    + echo '!!! error building specmd5sum'
    !!! error building specmd5sum
    + kill -TERM 1299
    + exit 1
    !!!!! buildtools killed

这次的问题出在getline函数上,该函数属于gcc的插件函数,也就是说既不是ANSI C标准,也不是POSIX标准,而是因为经常被使用而作为第三方的gnu扩展加入了gcc中。由于GLIBC 1中没有实现该方法,所以旧版本的程序需要自己实现该方法;而总从GLIBC2开始,该方法就写入了stdio.h中,再用老方法编译就会产生重定义的错误了。

解决该问题的方法之一是对gcc使用-stc=c99(其它C标准,比如C89等等也可以)让它不定义getline函数,而是使用自带的定义。但是这样引入的问题是,实际上SPEC其他组件是采用了gnu扩展标准的,而buildtools脚本又不支持对不同项目给出不同的命令行选项,反而又会导致其他项目编译失败。这里采用第二种方法,不使用自带的getline,而是使用gcc提供的getline函数。具体操作就是注释掉自带的getline.h头文件,参见该博客

  1. Perl的问题。

Perl这里一共有两个问题。第一个是asm/page.h头文件缺失的问题,可以直接注释掉。或许也可以使用-I/usr/include/x86_64-linux-gnu参数将该文件的新位置提供给gcc,但是我没有进行测试。

第二个是动态链接的问题。如果没有正确配置动态链接库libdl的话,Perl就无法提供动态加载模块的功能,导致在后续的Perl模块编译与测试阶段中出现类似的提示:

t/100generic-bzip2.....Can't load module Compress::Raw::Bzip2, dynamic loading not available in this perl.
  (You may need to build a new perl executable which either supports
  dynamic loading or has the Compress::Raw::Bzip2 module statically linked into it.)
 at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10
Compilation failed in require at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10.
BEGIN failed--compilation aborted at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Adapter/Bzip2.pm line 10.
Compilation failed in require at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Bzip2.pm line 11.
BEGIN failed--compilation aborted at /home/esper/cpu2006/tools/output/lib/perl5/site_perl/5.8.8/IO/Compress/Bzip2.pm line 11.
Compilation failed in require at t/100generic-bzip2.t line 12.
BEGIN failed--compilation aborted at t/100generic-bzip2.t line 12.

这里第二行给出了报错的原因:编译的Perl没有动态加载的功能,换句话说,就是用于动态加载代码的libdl库没有正确配置,导致动态加载一块的代码完全没有编译。回去翻阅Configure的输出,发现早在配置阶段就出现问题了:

dlopen() NOT found.
Do you wish to use dynamic loading? [n] 

由于完全没有给出任何有用的信息,加上咕果上也查不到什么有效的解决方案,在此陷入了困境。

然而,天无绝人之路,后来在解决另一个报错,数学库libm找不到的时候,意外地有了新的进展。可能是由于Configure脚本过老,无法识别到gcc数学库参数,额外附加库这项配置变成了none

Checking for optional libraries...
What libraries to use? [none]

由于实际上是使用了数学库的,导致在后续编译中会出现找不到符号的问题:

../libperl.a(pp.o): In function `Perl_pp_pow':
pp.c:(.text+0x27b6): undefined reference to `pow'
../libperl.a(pp.o): In function `Perl_pp_modulo':
pp.c:(.text+0x366d): undefined reference to `fmod'
../libperl.a(pp.o): In function `Perl_pp_atan2':
pp.c:(.text+0x824d): undefined reference to `atan2'
../libperl.a(pp.o): In function `Perl_pp_sin':
pp.c:(.text+0x8341): undefined reference to `sin'
../libperl.a(pp.o): In function `Perl_pp_cos':
pp.c:(.text+0x8481): undefined reference to `cos'
../libperl.a(pp.o): In function `Perl_pp_exp':
pp.c:(.text+0x87a1): undefined reference to `exp'
../libperl.a(pp.o): In function `Perl_pp_log':
pp.c:(.text+0x88e8): undefined reference to `log'
../libperl.a(pp.o): In function `Perl_pp_sqrt':
pp.c:(.text+0x8b9f): undefined reference to `sqrt'
../libperl.a(pp.o): In function `Perl_pp_crypt':
pp.c:(.text+0xabee): undefined reference to `crypt'
collect2: error: ld returned 1 exit status

看到前面提到的一篇博客中,通过修改buildtools的方法解决了该问题,并且我也进行了验证,确实有效。然而总觉得不够清真,为了解决一个系统上的问题修改了全局配置。后来了解到Perl的Configure可以通过hintfile为特定平台提供默认参数,从而解决了该问题。hintfile将会被用类似source的方式将变量引入当前脚本中,可以作为Configure选项的默认值。由于没有找到详细的文档,hintfile的变量名要自己猜,但是可以根据Configure中用于用户交互的信息进行变量名的猜测。在Linux系统中,Configure大概率会选择hints/linux.sh作为hintfile进行加载。

后来发现手动添加库依赖的变量名为libs,把-lm加入该变量值即可解决问题。

而后在阅读最新版Perl的hints/linux.sh(参见 https://github.com/Perl/perl5/blob/blead/hints/linux.sh )的时候发现,针对g++和dlopen有一段硬编码的配置:

# If using g++, the Configure scan for dlopen() and (especially)
# dlerror() might fail, easier just to forcibly hint them in.
case "$cc" in
*g++*)
  d_dlopen='define'
  d_dlerror='define'
  ;;
esac

其中提到g++和dlopen具有一定的不兼容性,Configure在这种条件下可能识别不出来libdl,抱着死马当活马医的心态,把这段配置复制到SPEC2006中Perl的hintfile中,发现没有任何变化,还是报出相同的错误。(显然不会有任何变化,默认使用的是cc/gcc命令,不会显式地拿g++来编译C代码的)

然后意识到问题之后,直接把里面的两行关键代码留下来,再次编译,报错发生了变化。

首先是通过hintfile的内置变量赋值,强行绕过了Configuredlopen检测。脚本给出了如下的警告信息,意味着虽然没有检测到libdl,但是程序忠实地按照变量配置,开启了含有libdl库的代码编译:

dlopen() NOT found.
*** WHOA THERE!!! ***
    The recommended value for $d_dlopen on this machine was "define"!
    Keep the recommended value? [y]
Do you wish to use dynamic loading? [y]

然后报出了新的错误:

        Making DynaLoader (static)
Processing hints file hints/linux.pl
Writing Makefile for DynaLoader
make[1]: Entering directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
make[1]: Leaving directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
make[1]: Entering directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
../../miniperl "-I../../lib" "-I../../lib" DynaLoader_pm.PL DynaLoader.pm
../../miniperl "-I../../lib" "-I../../lib" XSLoader_pm.PL XSLoader.pm
cp XSLoader.pm ../../lib/XSLoader.pm
cp DynaLoader.pm ../../lib/DynaLoader.pm
AutoSplitting ../../lib/DynaLoader.pm (../../lib/auto/DynaLoader)
rm -f DynaLoader.xs
cp dl_dlopen.xs DynaLoader.xs
../../miniperl "-I../../lib" "-I../../lib" ../../lib/ExtUtils/xsubpp -noprototypes -typemap ../../lib/ExtUtils/typemap DynaLoader.xs > DynaLoader.xsc && mv DyaLoader.xsc DynaLoader.c
cc -c   -I/home/esper/cpu2006/tools/output/include -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFSET_BITS=64 -O2   -DVERSION=\"1.05\" -DXS_VERSION=\"1.05\"  "-I../.."  -DPERL_CORE -DLIBC="" DynaLoader.c
rm -rf ../../lib/auto/DynaLoader/DynaLoader.a
/usr/bin/ar cr ../../lib/auto/DynaLoader/DynaLoader.a DynaLoader.o && : ../../lib/auto/DynaLoader/DynaLoader.a
chmod 755 ../../lib/auto/DynaLoader/DynaLoader.a
make[1]: Leaving directory `/home/esper/cpu2006/tools/src/perl-5.8.8/ext/DynaLoader'
cc -o perl  -L/home/esper/cpu2006/tools/output/lib -Wl,-E perlmain.o lib/auto/DynaLoader/DynaLoader.a  libperl.a `cat ext.libs` -lm -lcrypt -lutil -lc
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_find_symbol':
DynaLoader.c:(.text+0x3b3): undefined reference to `dlsym'
DynaLoader.c:(.text+0x441): undefined reference to `dlerror'
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_unload_file':
DynaLoader.c:(.text+0x4ea): undefined reference to `dlclose'
DynaLoader.c:(.text+0x559): undefined reference to `dlclose'
DynaLoader.c:(.text+0x566): undefined reference to `dlerror'
lib/auto/DynaLoader/DynaLoader.a(DynaLoader.o): In function `XS_DynaLoader_dl_load_file':
DynaLoader.c:(.text+0x645): undefined reference to `dlopen'
DynaLoader.c:(.text+0x6c9): undefined reference to `dlerror'
collect2: error: ld returned 1 exit status
make: *** [perl] Error 1
+ testordie 'error building Perl'
+ test 2 -ne 0
+ echo '!!! error building Perl'
!!! error building Perl
+ kill -TERM 550221
+ exit 1
!!!!! buildtools killed

我的眼神瞬间犀利了起来.jpg

undefined reference to ...,这不是最经典的忘记加-lXXX链接参数的结果吗?

除错终了后我神速开hintfile。目的唯一libs!!!!!我爆速修改链接选项。warning!
此处省略100余字

修改文件过后,果然编译成功!删去上文根据设想添加的两行d_XXX强制配置后,编译仍然通过,这证实了我的设想。

然而,Perl的测试中仍然有几个是处于失败状态的,大约1%,十来个左右。由于测试范围往往大于使用需求,对于这几个没通过的测试我就没理它了,直接yes进行后续编译操作。

总结就是,Perl编译动态加载模块失败的原因与数学库一样,都是因为识别不到系统配置,默认配置又不提供有效值造成的。在perl-5.8.8/hints/linux.sh末尾加上如下两行解决上述编译问题:

libs='-lm -lcrypt -lutil -lc -ldl'
libpth='/usr/local/lib64 /lib64 /usr/lib64'
  1. 自此之后编译上就应该没有本质上的问题了。如果是直接从.tar.gz文件解压的话,复制出来的src文件夹及下级文件权限都是444,即只有读权限,而后续某些模块的测试涉及到对测试文件的读写操作,会产生Permission Denied一类的报错信息。如果对自己的操作自信,不会损坏文件的话,可以使用偷懒的方法,直接给所有文件/文件夹写权限:
    sudo chmod u+w -R .

    tools/src目录下执行该命令即可。

中间还有个小插曲是,一开始以为是编译器版本的问题,整了gcc 4和5的docker来编译看能不能成功。结果不但没成功,还报出了新的You haven't done a "make depend" yet!错误,后来发现原因是gcc镜像的FROM用的是Debian系的,/bin/sh默认指向了dash而不是bash。用ln -snf /bin/bash /bin/sh覆盖修改这个链接即可。更多资料可参见 https://sjp38.github.io/post/spec_cpu2006_install/

ps. 如果在后续测试阶段发现某些测试怎么跑都跑不对,可以检查一下是不是拿到的SPEC有问题。我这里用到的SPEC是被实验室重新打包过的,里面好多原始数据文件被改过了,debug了好久才发现:(

本来想写一大堆,后来发现这件事情在理论上是不可行的,然后就放弃了。

首先的原因是了解到作为gcc的C++标准库,libstdc++是后向兼容的(这里的“后”指时间上的“过去”),也就是说,新版本libstdc++支持旧版本编译出来的程序,而反过来不行。这是因为libstdc++库是以一种类似于增量更新的方式实现的,使用strings指令查看libstdc++.so文件可以发现,它包含了从最早版本以来一系列的符号支持,这也正是其后向兼容的原因。

由于没有前向兼容,导致过去的gcc不是适应未来的编译环境。以我的体验为例,用gcc 8.4.1编译gcc 4.9.4,前者的libstdc++ABI应用二进制接口版本是CXXABI_1.3.9,而后者的只有CXXABI_1.3.8

而问题正是出在这里。由于gcc和libstdc++属于紧密耦合的关系,gcc会使用自带的libstdc++而不是当前系统的版本来提供运行环境。使用了gcc 8.4.1编译的gcc 4.9.4自然是按照前者的运行库,使用了CXXABI_1.3.9;而当gcc 4.9.4运行的时候,从自己的libstdc++库里一翻,只支持到CXXABI_1.3.8,啪的一下就因为找不到符号报错了,很快啊。

当然这个可以通过使用LD_PRELOAD强行让gcc 4.9.4使用系统libstdc++库来解决这个问题,但是后面还有更麻烦的。我前几年编译gcc的文档里提到过,gcc的编译是自举的,相当于先编译gcc核心-编译c++库-再编译gcc扩展这个流程。

然而,使用LD_PRELOAD只是解决了第二步中的一个问题,重点在于libstdc++在版本更新的时候,可能不止更新ABI,还更新了上层的API,可能编着编着发现函数不见了,或者签名改了,直接报错中断,问题还没等到链接阶段就已经出现了。要解决这个问题约等于更改gcc对应的libstdc++库依赖,这严重违反了两者现有的紧密依赖的特点。这也正是我选择放弃降级编译gcc最主要的原因。

最后的结论是,CentOS 8是个什么垃圾东西,CentOS 7+devtoolset它不香吗

在这些时候,我可以附和着笑,leader是决不责备的。而且leader见了孔乙己,也每每这样问他,引人发笑。孔乙己自己知道不能和他们谈天,便只好向孩子说话。有一回对我说道,“你写过代码么?”我略略点一点头。他说,“写过代码……我便考一考。C++的变量初始化,怎样写的?”我想,讨饭一样的人,也配考我么?便回过脸去,不再理会。孔乙己等了许久,很恳切的说道,“不能写罢?我教给你,记着!这些写法应该记着。将来做leader的时候,算KPI要用。”我暗想我和leader的等级还很远呢,而且leader也从不把C++语法算进KPI;又好笑,又不耐烦,懒懒的答他道,“谁要你教,不是一个等号的事么?”孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说,“对呀对呀!变量初始化有四样写法,你知道么?”我愈不耐烦了,努着嘴走远。孔乙己刚用指蘸了酒,想在柜上写代码,见我毫不热心,便又叹了口气,显出极惋惜的样子。

int val(4);
int val {4};    // *
int val = 4;
int val = {4};  // *

* 使用大括号{}进行变量初始化的功能来自于C++11标准,对于赋值过程有严格的检查,比如不允许使用类型缩窄(例如,把int类型值赋值给char类型变量,即使程序员知道这个int值可以使用char类型正确保存)。