• sprintf 格式代码使用不规范在不同平台下的表现


    sprintf 格式代码使用不规范在不同平台下的表现(C 语言)


     作者:高玉涵
     时间:2022.6.22 10:09
     博客:blog.csdn.net/cg_i

    时代变了,大人

    在这里插入图片描述

     像很多人一样我学习 C 语言是由 1978 年 Dennis M. RitchieBrian W. Kernighan 合著为《The C Programming Language》这本书走向 C 语言的世界。首先,书中描述的 C 是旧式的(有时称做 Kernighan 和 Ritchie[KERN 78],或称 K&R C)并已被 ANSI C (以下简称标准)从根本上取代了。尽管绝大多数以 K&R C 写成的程序仅需极微小的修改即可在 ANSI C 环境运行,但标准并未硬性规定 C 编译器对这些差异进行检查,而且绝大多数 C 编译器确实也不进行检查。因此,你必须自行保证编写的代码符合规范。如果使用不当,它们就会引发错误,导致细微而令人困惑的症状,并且极难发现原因。只是略知一二便放手使用是件非常危险的事。如果那样的话,它给带来的总是痛苦而不是欢乐。

    正文

    1. 使用环境

    • CPU

     HPUNIX:安腾 9700 系列

     LINUX:X86 架构

    • 操作系统

     HPUNIX:HP-UX xxxxx2 B.11.31 U ia64 2932500072 unlimited-user license

     LINUX:Linux xxxxkf1 4.19.90-24.4.v2101.ky10.x86_64 #1 SMP Mon May 24 12:14:55 CST 2021 x86_64 x86_64 x86_64 GNU/Linux

    • 编译器程序及版本

     HPUNIX:cc: HP C/aC++ B3910B A.06.25 [Nov 30 2009]

     LINUX:

    使用内建 specs。
    COLLECT_GCC=gcc
    COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-linux-gnu/7.3.0/lto-wrapper
    目标:x86_64-linux-gnu
    配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,lto --enable-plugin --enable-initfini-array --disable-libgcj --without-isl --without-cloog --enable-gnu-indirect-function --build=x86_64-linux-gnu --with-stage1-ldflags=' -Wl,-z,relro,-z,now' --with-boot-ldflags=' -Wl,-z,relro,-z,now' --with-tune=generic --with-arch_32=x86-64 --disable-multilib
    线程模型:posix
    gcc 版本 7.3.0 (GCC) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.代码展示

     先看示例代码:

    #include<stdio.h>
    
    int main(void)
    {
      char sPNGZXH[100];
      int PNGZXH_LEN = 11;
    
      sprintf(sPNGZXH, "%0*s",PNGZXH_LEN,"123");
      printf("%s\n", sPNGZXH);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • HPUNIX 编译并运行
    cc sp.c -o sp
    ./sp
    00000000123
    
    • 1
    • 2
    • 3
    • LINUX 编译并运行
    gcc sp.c -o sp
    ./sp
            123
    
    • 1
    • 2
    • 3

    sprintf

     int sprintf( char *buffer, char const *format, … );

     sprintf 把它的结果作为一个 NUL 结尾的字符串存储在指定的 buffer 缓冲区而不是写入到流中。函数返回值是实际打印或存储的字符数。

    警告

    sprintf 是一个潜在的错误根源。缓冲区的大小并不是 sprintf 函数的一个参数,所以如果输出结果很长溢出缓冲区时,就可能改写缓冲区后面内存位置中的数据。要杜绝这个问题,可以采取两种策略。第 1 种是声明一个非常巨大的缓冲区,但这个方案很浪费内存,而且尽管大型绶冲区能够减少溢出的可能性,但并不能根除这种可能性。第 2 种方法是对格式进行分析,看看最大可能出现的值被转换后的结果输出将有多长。例如,在 4 位整型的机器上,最大的整数有 11 位(包括一个符号位),所以缓冲区至少能容纳 12 个字符(包括结尾的 NUL 字节)。字符串的长度并没有限制,但函数所生成的字符串的字符数目可以用格式化代码中一个可选的字段来限制。

    格式代码

     sprintf 函数原型中的 format 字符串可能包含格式代码,它使参数列表的下一个值根据指定的方式进行格式化,至于其他的字符则原样逐字打印。格式代码由一个百分号开头,后面跟(1)零个或多个标志字符,用于修改有些转换的执行方式,(2)一个可选的最小字段宽度,(3)一个可选的精度,(4)一个可选的修改符,(5)转换类型。

    3. 问题描述

     有了上面背景铺垫,再看代码在两个平台下的输出差异是明显的。HPUNIX 下的输出结果是当前我们一直使用且想要的。然而代码移植到 LINUX 平台后,原先正常的代码(看起来像)现在就不能用了(字符串右对齐使用 0 填充左边未使用的列,变成了空格)。

     首先看一下标准是如何描述标志字符和它们的含义(这里只列出我们关心的)。

    代码参数含义
    schar *打印一个字符串
    标志含义
    0当数值为右对齐时,缺省情况下是使用空格填充未使用的列。这个标志表示用零来填充,它可用于 d,i,u,o,x,X,e,E,f,g 和 G 代码。使用 d,i,u,o,x 和 X 代码时,如果给出了精度字段,零标志就被忽略。如果格式化代码中出现了负号标志,零标志也没有效果。
    宽度含义
    *宽度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。

    总结

     通过认真研读标准标志 0 并不适用于 s 代码,LINUX 只是严格按标准以正确的格式输出值,反而 HPUNIX 下的输出结果是错误的。产生这种错误的根源:是典型过度依赖某些编译器特性(HPUNIX 下的 cc)写下了不可移植的代码。 这会为以后程序的运行、移植埋下隐患。

    参考

  • 相关阅读:
    thinkphp5.0 composer 安装oss提示php版本异常
    大数据Flink(九十七):EXPLAIN、USE和SHOW 子句
    micropython RP2040/esp32/c3自编译4MB/8MB/16MB固件分享
    Linux学习笔记(7) -- 文件管理(中)
    关于 sap.ui.base.Object 的简要介绍
    uboot源码分析(基于S5PV210)之启动第一阶段
    顾樵 量子力学I 导读(1)
    以全新的视角审视重构——世界软件大师“鲍勃大叔”作序推荐
    【wpf】 当用了数据模板之后如何获取控件的Item?
    交通大模型与时序大模型整理【共15篇工作】【附开源代码】
  • 原文地址:https://blog.csdn.net/cg_i/article/details/125414576