2021年7月

最近在看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它不香吗

在读某篇文章的时候提到了乱序处理器的执行架构,其中有一个instruction commit/retire的概念没能很好地理解。后来在Wikipedia上看到了一段简短的文字(参见 https://en.wikipedia.org/wiki/Instruction_window ),有了一个初步的认识,在此记录一下。

首先是instruction window指令窗口(同ROB,Re-Order Buffer,重排序缓冲区)的概念。根据介绍,乱序执行模型实际上是一种整体上顺序、局部乱序的执行模型,允许处理器在执行某一指令时试探性地执行接下来的一些指令。为了确保尽可能大的预测有效率,处于乱序执行中的指令必须等待自己的操作数就绪才可以执行。

由于试探性执行的结果可能出现错误,而后需要通过状态回滚来撤销错误的执行操作,所以处于试探性执行阶段的指令需要单都标记出来,这便是指令窗口。处在指令窗口中的指令都处于执行中的状态,既可能是在正常按顺序执行,也可能是在乱序执行技术下提前执行。出于硬件设计合理性、执行效率等因素考虑,允许处于乱序执行状态下的指令数应该是有限的,也就意味着指令窗口是有大小限制的。

如果指令窗口中的指令执行结果确认无误,则可以提交执行结果,离开指令窗口,这个动作在英语中就称作instruction commit/retire。此时,下一条指令可以进入指令窗口,开始执行。

https://zhidao.baidu.com/question/1384876083010201380.html

chine这个词其实源于拉丁语的sina词根,法语可能把这个阴性也继承过去了,英语最明显China。就因为a结尾的在罗曼语及很多屈折语里多半都是阴性。而日本的发音来自汉字日本两字,最后本字有个鼻音韵尾,日本音读也继承了汉语的读法,而这个鼻音韵尾在屈折语里又多是阳性。原因大概就在这里。印欧语除了一些典型的可分阴阳的事物以外,大部分名词的阴阳都是随意分的。阴性、阳性也是翻译出来的说法。有的屈折语还有好几个性。

那为啥中国又是sina呢?渣浪爬啊

https://www.zhihu.com/question/382819849/answer/1109285663

https://www.etymonline.com/word/Sino- :

Sino- before vowels Sin-, word-forming element meaning "Chinese," 1879, from Late Latin Sinæ (plural) "the Chinese," from Ptolemaic Greek Sinai, from Arabic Sin "China," probably from Chinese Ch'in, name of the fourth dynasty of China (see China).

拉丁语的「中国」是 Sinae (复数,理论上的单数当写作:Sina),源自托勒密希腊语的 Sinai,而托勒密希腊语的 Sinai 进而源自阿拉伯语Sin,最初的所指可能是汉语的「秦」。

但这哥们又提出了自己的观点:

疑点一:托勒密时代(前305-前30)的阿拉伯人尚处于蒙昧期,也没有与中国的直接接触,很难想象他们会先于希腊人知道中国。张骞第二次出使西域(前119),他的副使最西到达了安息(波斯帕提亚王朝)。安息与希腊人的塞琉古帝国接壤,塞琉古曾经统治整个波斯,安息就是由造反的波斯帕提亚部族所建立。当时波斯虽然大体「光复」,然而中亚、南亚仍有不少希腊的殖民王国存在。张骞副使所探访的身毒(印度)就是希腊的印度王国,而与月氏毗邻的大夏很可能就是希腊的巴克特里亚王国。所以说,托勒密的希腊人如果这个时候认识了中国,要么是从中亚的希腊人口中得知,要么是从波斯人、塞人、粟特人或者印度人这些跟汉已经有过直接接触的人口中得知。既没有直接接触、文化又较落后的阿拉伯人似乎排不上号。

疑点二:「秦」字是浊音声母,上古拟音作 * zin(郑张尚芳),中亚各族语言、希腊语、印度诸语中都存在浊擦音,不大可能把 zin 讹读成 sin 。反过来,「晋」是清音声母,上古拟音作 * ʔsins,(汉代时去声的后缀-s可能已经弱化成-h),Sinai更像是从这个音来的。