批处理IF/FOR语句的变量扩展


在windows下使用批处理脚本时,遇到了这样一个问题,FOR循环内部的变量设置出错:当全局存在同名变量时,echo该变量得到全局变量的值,当全局不存在该变量时,echo显示出来该变量为空。以下述bat代码为例:

    @echo off
    rem variable expansion problem
    FOR /L %%i in (1, 1, 3) do (
        set /a square=%%i*%%i
        echo "square(%%i) = %square%" 
    )
    pause
    @echo on
    -------------------------------
    Result:
    "square(1) = "
    "square(2) = "
    "square(3) = "
                

查阅资料Environment Variable Expansion发现,bat脚本执行过程会发生变量替换。cmd.exe每读取一条命令时,会先将变量替换成变量的值,然后再执行该命令。 然而吊诡的是,只有一整条IF/FOR语句才会被当成一条完整的语句,因而IF/FOR内部赋值操作在时间上后于echo语句中变量替换操作。
解决问题的方法是:变量延迟,即在FOR/IF语句前使用setlocal EnableDelayedExpansion语句,并且在引用变量时采用!var_name!的形式,即可解决这个问题,如下代码所示。据说,这是新手面对bat脚本最坑爹的问题之一。

    @echo off
    rem variable expansion problem
    setlocal EnableDelayedExpansion
    FOR /L %%i in (1, 1, 3) do (
        set /a square=%%i*%%i
        echo "square(%%i) = !square!" 
    )
    pause
    @echo on
    -------------------------------
    Result:
    "square(1) = 1"
    "square(2) = 4"
    "square(3) = 9"
                

unsigned long int长度


unsigned long int为C/C++长整型,其长度至少为32bit。在32bit系统其长度均为32bit,但是在64bit系统中,其长度与编译器实现有关,有些编译器上其长度为64bit,有些编译器上其长度为32bit,故在使用时需要引起注意。我们使用如下代码测试在mingw64(win10系统)和gcc(linux系统)中,相关类型的长度:

    #include 

    int main(void) {
        printf("sizeof(unsigned int):%d\n", sizeof(char *));
        printf("sizeof(unsigned long int): %d\n", sizeof(unsigned long int));
        printf("sizeof(unsigned long long): %d\n", sizeof(unsigned long long));
        printf("sizeof(char *): %d\n", sizeof(char *));
        return 0;
    }
                

结果表明:

因而在实际开发中,准确不同类型的长度能够避免不必要的bug出现,当然为了跨平台跨编译器的一致性,我们最好使用uint32_t,uint64_t类型表示长整型。

文本形式打开二进制文件


在高级数据库课程实验中使用c语言的文件读写接口时遇到过这样一个bug:程序在开始时创建一个初始数据库文件,通过fwrite接口按页大小写回页面,这些页面包含目录页和数据页,如下图所示。程序在windows上遇到这样的问题,创建好初始文件后,发现第一页目录页多出来4字节的数据,后续页面也有相应的额外数据,但是在linux系统下却没有这个问题。

    FILE * dbf = fopen(filename.c_str(), "w+"); // open the database file, should open with mode "wb+"

    if(dbf == NULL) {
        cout << "Create file failed " <<  endl;
    }

    uint32_t data_page_cnt = 0;

    char * buf = new char[FRAMESIZE];
    auto dir_page = (Page *) buf;

    //materialize all the directory page
    for(int i = 0; i < DIRSIZE; i++) {
        for(int j = 0; j < PAGE_DIR_ENTRYS; j++) {
            dir_page->tuples[j] = data_page_cnt >= MAXPAGES ? 0 : DIRSIZE + data_page_cnt++;
        }
        dir_page->tuple_num = PAGE_DIR_ENTRYS;
        fwrite(buf, 1, FRAMESIZE, dbf); 
    }
                

    经过调试发现,是因为创建数据文件使用了"w+" mode,这表示文件以文本形式打开,故在window系统下,读写文件接口会额外做这样一件事情:将数据中的"0A"全部替换成"ODOA",即windows下的换行符"\cr\n",而在linux系统中不会插入额外数据。因此修改程序的打开mode为"wb+"之后,程序不在出现类似的bug。

使用openMP时,omp_set_num_threads()接口不起作用


OpenMP提供了一系列轻量的并行编程模型, 方便我们将局部的串行任务并行化。 最简单的并发编译制导语句有#pragma omp parallel for ,该语句下面可以接一段不存在值依赖for循环语句,openMP启动多个子线程切分上述循环,达到并行执行for循环的效果。 程序员可在串行代码处使用 omp_set_num_threads()接口 设置for循环执行的线程数量。

在某个项目开发过程中,本人发现了在一段代码中无论怎么设置openMP线程数量,for循环仍然是串行执行的,使用omp_get_num_threads()永远返回1。 最后通过调试定位到了问题所在: 项目编译分为两个部分——源文件编译和obj文件链接。 在obj文件链接时,使用了-fopenmp选项,但是源文件编译时忘了开启该选项,导致obj文件永远生成的是串行的版本。

因此本次bug得到的启示是,当使用openMP进行并行编程时,无论是编译还是链接过程,都需要显式指定-fopenmp选项