C标准库中的接口如strlen被优化现象分析

故事简述

这里主要想解释的是两个现象:

  1. test_setvbuf.cfputs被改写为fwrite
  2. strlen会被直接优化为具体的大小

两个现象其实是一个问题,这里以strlen来阐述说明。

问题分析

先看代码:

    char a[10]={'x',};
    lena = strlen(a);
    printf("%s,%d\r\n",a,lena);
    for (i = 0; i < 10; i++)
    {
        a[i] = i+1;
    }
    lena = strlen(a);
    printf("%s,%d\r\n",a,lena);

里面有两个长度strlen:

  • 第一个长度计算的时候,并没有bl <strlen>,而是直接返回了长度1
  • 第二个长度计算的时候,是有bl <strlen>

最开始,我们以为是优化级别的原因,于是把o2改成了o0,结果还是同样的问题。

这让人非常疑惑,编译器也太智能了吧,但为啥我都不让你帮我优化了,你还是要优化呢?

这里就得从gcc着手了,于是我们让智谱清言来分析一下它的配置:

Target: aarch64-none-elf
Configured with: /data/jenkins/workspace/GNU-toolchain/arm-10-4/src/gcc/configure --target=aarch64-none-elf --prefix=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/install// --with-gmp=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/host-tools --with-mpfr=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/host-tools --with-mpc=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/host-tools --with-isl=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/host-tools --disable-shared --disable-nls --disable-threads --disable-tls --enable-checking=release --enable-languages=c,c++,fortran --with-newlib --with-libiconv-prefix=/data/jenkins/workspace/GNU-toolchain/arm-10-4/build-mingw-aarch64-none-elf/host-tools --host=i686-w64-mingw32 --with-pkgversion='GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)' --with-bugurl=https://bugs.linaro.org/
Thread model: single
Supported LTO compression algorithms: zlib
gcc version 10.3.1 20210621 (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29))

这里我们看到了一个参数:

  • --with-newlib: 使用Newlib作为标准库。
nostdlib用法

于是问道:如果配置 gcc时使用了–with-newlib,那么在使用strlen的时候,是使用gcc的呢,还是自己写好的strlen呢?

这里就得说说库的链接顺序,但我们的musl库是和内核一起编译的,因此肯定会先链接我们自己的musl库,而后如果musl库里面没有该接口,才回去gcc内置库里面去寻找。

而且我们在链接阶段使用了:-nostdlib,这会让我们告诉编译器不要链接标准库(如C库、数学库等)。

fno-builtin用法

那么问题来了,为什么gcc编译strlen调用的时候,既没有用内建的,也没有用我定义的,而是直接给出了长度?

如果您发现GCC在编译时没有调用内建的strlen函数,也没有使用您定义的strlen实现,而是直接给出了长度,这可能是由于编译器优化导致的。GCC有一个内置的strlen函数,但在某些情况下,编译器可能会执行一种称为常量折叠的优化,尤其是在它能够确定字符串的长度在编译时是已知的情况下。

那么如何禁用内置函数的优化呢?

编译器特定选项: 使用编译器特定的选项来禁用特定类型的优化。例如,在GCC中,您可以使用-fno-builtin来禁用内置函数的优化。

-fno-builtin是GCC编译器的一个选项,用于控制编译器对内置函数的处理方式。在C语言中,有些函数是作为编译器内置的,这意味着编译器会直接将它们替换为相应的机器代码,而不是生成函数调用的代码。这通常用于性能优化,因为内置函数的调用开销更小,而且编译器可以针对具体的调用情况进行优化。

当您使用-fno-builtin选项时,您是在告诉GCC不要将这些内置函数当作内置函数处理,而是按照普通的函数调用方式来编译它们。这意味着编译器会生成函数调用的代码,而不是直接替换为机器代码。

内置函数替换

关于内置函数,编译器会直接将它们替换为相应的机器代码,编译器是怎么知道的呢

编译器知道内置函数的实现并将其替换为机器代码,是因为这些函数的实现是编译器的一部分,而不是从外部库中链接进来的。这些内置函数通常包括C语言标准库中的函数(如printfscanfstrlen等),以及编译器提供的其他常用函数(如数学函数、字符串处理函数等)。

当编译器解析源代码时,它会识别出这些内置函数的调用。编译器内部有一个预定义的内置函数列表,这些函数的实现通常位于编译器的libgcclibstdc++库中。当编译器遇到一个内置函数调用时,它会将这个调用替换为对应的机器代码,而不是生成一个函数调用。

例如,当编译器遇到以下C代码时:

printf("Hello, World!");

它会将printf调用替换为libgccprintf函数的机器代码,而不是生成一个函数调用。这使得调用更加快速,因为不需要跳转到外部函数的地址,也不需要执行额外的指令来执行函数调用。

内置函数的替换通常发生在编译器的前端,也就是在编译器的词法分析器(lexer)、语法分析器(parser)和语义分析器(semantic analyzer)阶段。在这些阶段,编译器解析源代码,识别出函数调用,并决定是否将其替换为内置函数的实现。

请注意,内置函数的替换是编译器优化的一部分,并不是所有编译器都会自动执行。在某些情况下,特别是当您使用特定的编译器选项(如-fno-builtin)时,编译器可能不会进行内置函数的替换。此外,内置函数的替换也受到编译器配置和目标平台的影响。

O0取消替换?

-O0可以取消内置函数的替换吗

不,-O0选项不能取消内置函数的替换。-O0是GCC的优化级别选项,它表示不进行任何优化。然而,内置函数的替换是编译器默认的行为,即使在-O0优化级别下,内置函数仍然会被替换为机器代码。

内置函数的替换是编译器实现的一部分,旨在提供高性能的调用,而不需要额外的函数调用开销。这种优化是编译器自动进行的,不受优化级别的影响。

如果您想要禁用内置函数的替换,您需要使用特定的编译器选项。例如,使用-fno-builtin选项可以告诉GCC不要将某些函数作为内置函数处理。这个选项可以应用于特定的函数,也可以应用于所有的内置函数。

例如,如果您想要禁用对printf函数的内置处理,可以使用以下命令:

gcc -fno-builtin-printf your_file.c -o your_program

这将告诉GCC不要将printf函数作为内置函数处理,而是生成一个函数调用。请注意,这可能会影响性能,因为函数调用通常比内置函数的替换慢。

  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值