cstdio的源码学习分析11-格式化输入输出函数fprintf---format解析跳转表逻辑分析

160 阅读11分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

背景

在stdio.h的printf类型的函数中,要完成变量打印,主要有两个核心任务:一个是识别format占位符构造printf_info结构体,一个是打印该种特定类型的变量,这个跳转表机制就是为了识别format占位符而创建的。

%[flags][width][.precision][length]specifier对于这样的一个字符串类型识别,我们通常会采用自动机的方式进行识别,设定多个状态,每个状态代表一个识别位,状态之间的跳转需要条件,直到最后识别到specifier类型,这时,我们就能够完全解析出所有字段,上面构想的方法其实就是有限状态自动机的方法。

但是,因为C标准中规定了上面五个字段中可以有很多种组合,这个状态数量的叠加将是非常巨大的,我们能否寻找到一种比较简单的方式实现这个逻辑呢?

答案是有的,在C语言中有一种语法深为人所诟病,那就goto语句,可以进行无条件跳转到设定好的标号处,当然大部分情况下我们是不建议使用的,但是它在这种场景下就很契合,试想一下,我们的有限状态自动机中每一个状态是否就是一个标号呢?在这个标号中处理当前符号的识别,处理完成之后,移动到下一个字符,并通过我们设定好的跳转表跳转到下一个标号处进行处理,这个过程中,我们需要维护好的就是这个关键的跳转表,它记录了当前符号的下一个处理标号的位置,跳转表充当了有限状态自动机中触发状态变化函数的作用。

跳转表相关的宏定义/变量

1.jump_table---字符对应的跳转位置,字符在L_(' ')和L_('z')之间

其实这些字符的设定规律也比较明显:

  • 跳转值为0代表在format字符串解析过程中没有特殊含义的字符:如大写的LMNOPQ等;
  • 跳转值为1代表' ',2代表'+',3代表'-',4代表'#',5代表'0',6代表'',7代表'*',9代表'.';
  • 跳转值8代表数字1到9;
  • 跳转值10代表'h'(类型前缀,表示长度扩展信息,如hd表示short int);
  • 跳转值11代表'l'(类型前缀,表示长度扩展信息,如ld表示long int);
  • 跳转值12代表'L'和'q''(类型前缀,表示长度扩展信息,如Lf表示long long double)
  • 跳转值13代表'Z'和'z',size_t类型
  • 跳转值14代表'%'
  • 跳转值15代表'd'和'i',有符号整型数据
  • 跳转值16代表'u',无符号整型数据
  • 跳转值17代表'o',无符号八进制整型数据
  • 跳转值18代表'X'和'x',无符号16进制整型数据
  • 跳转值19代表'E', 'e', 'F', 'f', 'G', 'g',浮点数
  • 跳转值20代表'c',字符类型
  • 跳转值21代表's', 'S',字符串类型
  • 跳转值22代表'p',指针类型
  • 跳转值23代表'n',数字类型
  • 跳转值24代表'm',标准错误类型
  • 跳转值25代表'C',宽字符类型
  • 跳转值26代表'A', 'a',浮点数16进制类型
  • 跳转值27代表't',ptrdiff_t类型
  • 跳转值28代表'j',intmax_t类型
  • 跳转值29代表'I',标识是否使用额外的字符解释,如在标准字符中使用到了宽字符
  • 跳转值30代表'B', 'b',二进制整型数据
/* This table maps a character into a number representing a class.  In
   each step there is a destination label for each class.  */
static const uint8_t jump_table[] =                                                                                                                      
  {
    /* ' ' */  1,            0,            0, /* '#' */  4,
           0, /* '%' */ 14,            0, /* '''*/  6,
           0,            0, /* '*' */  7, /* '+' */  2,
           0, /* '-' */  3, /* '.' */  9,            0,   
    /* '0' */  5, /* '1' */  8, /* '2' */  8, /* '3' */  8,
    /* '4' */  8, /* '5' */  8, /* '6' */  8, /* '7' */  8,
    /* '8' */  8, /* '9' */  8,            0,            0,   
           0,            0,            0,            0,   
           0, /* 'A' */ 26, /* 'B' */ 30, /* 'C' */ 25,
           0, /* 'E' */ 19, /* F */   19, /* 'G' */ 19,
           0, /* 'I' */ 29,            0,            0,   
    /* 'L' */ 12,            0,            0,            0,   
           0,            0,            0, /* 'S' */ 21,
           0,            0,            0,            0,   
    /* 'X' */ 18,            0, /* 'Z' */ 13,            0,   
           0,            0,            0,            0,   
           0, /* 'a' */ 26, /* 'b' */ 30, /* 'c' */ 20,
    /* 'd' */ 15, /* 'e' */ 19, /* 'f' */ 19, /* 'g' */ 19,
    /* 'h' */ 10, /* 'i' */ 15, /* 'j' */ 28,            0,   
    /* 'l' */ 11, /* 'm' */ 24, /* 'n' */ 23, /* 'o' */ 17,
    /* 'p' */ 22, /* 'q' */ 12,            0, /* 's' */ 21,
    /* 't' */ 27, /* 'u' */ 16,            0,            0,   
    /* 'x' */ 18,            0, /* 'z' */ 13
  };

2.NOT_IN_JUMP_RANGE(Ch)---判断字符是否在跳转范围内

因为我们规定可以识别跳转的字符在L_(' ')和L_('z')之间,其他字符均视为未知字符

#define NOT_IN_JUMP_RANGE(Ch) ((Ch) < L_(' ') || (Ch) > L_('z'))

3.CHAR_CLASS(Ch)---获取对应的跳转值

因为jump_table是按照L_(' ')和L_('z')之间编写的,所以将对应字符值减去L_(' ')可以得到索引值,进而取出对应的跳转值

#define CHAR_CLASS(Ch) (jump_table[(INT_T) (Ch) - L_(' ')])

4.LABEL(Name)---label宏

定义label的格式,增加do_前缀

如:LABEL (flag_space):表示do_flag_space:label

#define LABEL(Name) do_##Name

5.JUMP_TABLE_TYPE---跳转表中存储的数据类型

# define JUMP_TABLE_TYPE const int

6.JUMP_TABLE_BASE_LABEL---基础label

即do_form_unknown,未知格式跳转

# define JUMP_TABLE_BASE_LABEL do_form_unknown

7.REF(Name)---计算与基础label的地址差

C语言中可以使用&&符号获取标签名的地址

# define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL

8.JUMP(ChExpr, table)---根据表与输入字符进行跳转

  1. 首先计算当前输入字符的偏移:如果是有效字符,则返回表的对应跳转位置,否则返回REF (form_unknown),即0;
  1. 取得要跳转的标号的地址base+offset;
  1. 进行跳转 goto *ptr。

这里其实可以把标号当做函数指针,goto语句类似函数调用。

# define JUMP(ChExpr, table)                              \
      do                                      \
    {                                     \
      int offset;                                 \
      void *ptr;                                  \
      spec = (ChExpr);                            \
      offset = NOT_IN_JUMP_RANGE (spec) ? REF (form_unknown)          \
        : table[CHAR_CLASS (spec)];                       \
      ptr = &&JUMP_TABLE_BASE_LABEL + offset;                 \
      goto *ptr;                                  \
    }                                     \                                                                                                              
      while (0)

核心跳转表定义

以下跳转表都定义在STEP0_3_TABLE宏中

1.step0_jumps[31]---开始进行format占位符识别或识别到flags时使用

仔细看,我们可以看到,这里的顺序与jump_table的标号是保持一致的,比如我们前面的例子中%c,在此处进行跳转时,会直接跳转到REF (form_character),针对这样的简单format,一般在这里就跳转成功了,进行相应的打印。

    /* Step 0: at the beginning.  */                          \
    static JUMP_TABLE_TYPE step0_jumps[31] =                      \
    {                                         \
      REF (form_unknown),                             \
      REF (flag_space),     /* for ' ' */                     \
      REF (flag_plus),      /* for '+' */                     \
      REF (flag_minus),     /* for '-' */                     \
      REF (flag_hash),      /* for '<hash>' */                \
      REF (flag_zero),      /* for '0' */                     \
      REF (flag_quote),     /* for ''' */                    \
      REF (width_asterics), /* for '*' */                     \
      REF (width),      /* for '1'...'9' */               \
      REF (precision),      /* for '.' */                     \
      REF (mod_half),       /* for 'h' */                     \
      REF (mod_long),       /* for 'l' */                     \
      REF (mod_longlong),   /* for 'L', 'q' */                \
      REF (mod_size_t),     /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_float),     /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_character), /* for 'c' */                     \
      REF (form_string),    /* for 's', 'S' */                \
      REF (form_pointer),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_strerror),  /* for 'm' */                     \
      REF (form_wcharacter),    /* for 'C' */                     \
      REF (form_floathex),  /* for 'A', 'a' */                \                                                                                          
      REF (mod_ptrdiff_t),      /* for 't' */                     \
      REF (mod_intmax_t),       /* for 'j' */                     \
      REF (flag_i18n),      /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    };

那么在哪些情况下会使用到这张表呢?通过代码中的分析,可以看到如下几种情况:

因为针对合法字符格式,在这些字符后跟着的应该是step0_jumps这样对应的格式,此时识别的就是flags相关的字符

format占位符的一般形式:%[flags][width][.precision][length]specifier

(1).识别了%符号后的第一个字符

      /* Get current character in format string.  */
      JUMP (*++f, step0_jumps);

(2).识别空格符之后

      /* ' ' flag.  */
    LABEL (flag_space):
      space = 1;
      JUMP (*++f, step0_jumps);

(3).识别'+'之后

      /* '+' flag.  */
    LABEL (flag_plus):
      showsign = 1;
      JUMP (*++f, step0_jumps);

(4).识别'-'之后

      /* The '-' flag.  */
    LABEL (flag_minus):
      left = 1;
      pad = L_(' ');
      JUMP (*++f, step0_jumps);

(5).识别'#'之后

      /* The '#' flag.  */
    LABEL (flag_hash):
      alt = 1;
      JUMP (*++f, step0_jumps);

(6).识别‘0’之后

      /* The '0' flag.  */
    LABEL (flag_zero):
      if (!left)
    pad = L_('0');
      JUMP (*++f, step0_jumps);

(7).识别'''之后


      /* The ''' flag.  */
    LABEL (flag_quote):
      group = 1;
      ...
    JUMP (*++f, step0_jumps);

(8).识别'I'之后

    LABEL (flag_i18n):
      use_outdigits = 1;
      JUMP (*++f, step0_jumps);

2.step1_jumps[31]---识别到width相关的字符时使用

注意这个表与step0_jumps的对比,我们可以发现,如下字符对应的位置,都被置为了REF (form_unknown):

  • ' ','+','-','#','0',''','I'---因为在flags识别中出现了,后续不可能再出现了,如果出现,则认为是异常模式;
  • '*','1'...'9'---是width的识别关键字符,识别出现后,将会把相关字符取出,后续如果继续出现,那说明是异常模式
    /* Step 1: after processing width.  */                    \
    static JUMP_TABLE_TYPE step1_jumps[31] =                      \
    {                                         \
      REF (form_unknown),                             \
      REF (form_unknown),   /* for ' ' */                     \
      REF (form_unknown),   /* for '+' */                     \                                                                                          
      REF (form_unknown),   /* for '-' */                     \
      REF (form_unknown),   /* for '<hash>' */                \
      REF (form_unknown),   /* for '0' */                     \
      REF (form_unknown),   /* for ''' */                    \
      REF (form_unknown),   /* for '*' */                     \
      REF (form_unknown),   /* for '1'...'9' */               \
      REF (precision),      /* for '.' */                     \
      REF (mod_half),       /* for 'h' */                     \
      REF (mod_long),       /* for 'l' */                     \
      REF (mod_longlong),   /* for 'L', 'q' */                \
      REF (mod_size_t),     /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_float),     /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_character), /* for 'c' */                     \
      REF (form_string),    /* for 's', 'S' */                \
      REF (form_pointer),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_strerror),  /* for 'm' */                     \
      REF (form_wcharacter),    /* for 'C' */                     \
      REF (form_floathex),  /* for 'A', 'a' */                \
      REF (mod_ptrdiff_t),      /* for 't' */                     \
      REF (mod_intmax_t),       /* for 'j' */                     \
      REF (form_unknown),       /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    };

使用表的情况:

(1).识别到'*'之后

      /* Get width from argument.  */
    LABEL (width_asterics):
    ...
    JUMP (*f, step1_jumps);

(2).识别到'1'...'9'之后

      /* Given width in format string.  */
    LABEL (width):
    ...
    JUMP (*f, step1_jumps);

3.step2_jumps[31]---识别到precision相关字符时使用

对比step1_jumps可以发现,'.' 位置被置为REF (form_unknown)

    /* Step 2: after processing precision.  */                    \
    static JUMP_TABLE_TYPE step2_jumps[31] =                      \
    {                                         \
      REF (form_unknown),                             \
      REF (form_unknown),   /* for ' ' */                     \
      REF (form_unknown),   /* for '+' */                     \
      REF (form_unknown),   /* for '-' */                     \
      REF (form_unknown),   /* for '<hash>' */                \
      REF (form_unknown),   /* for '0' */                     \
      REF (form_unknown),   /* for ''' */                    \
      REF (form_unknown),   /* for '*' */                     \
      REF (form_unknown),   /* for '1'...'9' */               \
      REF (form_unknown),   /* for '.' */                     \
      REF (mod_half),       /* for 'h' */                     \
      REF (mod_long),       /* for 'l' */                     \
      REF (mod_longlong),   /* for 'L', 'q' */                \
      REF (mod_size_t),     /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_float),     /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_character), /* for 'c' */                     \
      REF (form_string),    /* for 's', 'S' */                \
      REF (form_pointer),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_strerror),  /* for 'm' */                     \
      REF (form_wcharacter),    /* for 'C' */                     \
      REF (form_floathex),  /* for 'A', 'a' */                \                                                                                          
      REF (mod_ptrdiff_t),      /* for 't' */                     \
      REF (mod_intmax_t),       /* for 'j' */                     \
      REF (form_unknown),       /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    };

(1)识别到'*'后

    LABEL (precision):
    ...
    JUMP (*f, step2_jumps);

4.step3a_jumps[31]---识别到第一个'h'修饰符之后使用

对比step2_jumps之后发现:

  • 原来'h'的位置从REF (mod_half)变为REF (mod_halfhalf),为了识别'hh'的情况
  • 'l','L','q','z','Z','E','e','F','f','G','g','c','s','S','p','m','C',’a’,'A','t','j',都被置为REF (form_unknown),因为这些都是非法组合
    /* Step 3a: after processing first 'h' modifier.  */              \
    static JUMP_TABLE_TYPE step3a_jumps[31] =                     \
    {                                         \
      REF (form_unknown),                             \
      REF (form_unknown),   /* for ' ' */                     \
      REF (form_unknown),   /* for '+' */                     \
      REF (form_unknown),   /* for '-' */                     \
      REF (form_unknown),   /* for '<hash>' */                \
      REF (form_unknown),   /* for '0' */                     \
      REF (form_unknown),   /* for ''' */                    \
      REF (form_unknown),   /* for '*' */                     \
      REF (form_unknown),   /* for '1'...'9' */               \
      REF (form_unknown),   /* for '.' */                     \
      REF (mod_halfhalf),   /* for 'h' */                     \
      REF (form_unknown),   /* for 'l' */                     \
      REF (form_unknown),   /* for 'L', 'q' */                \
      REF (form_unknown),   /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_unknown),   /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_unknown),   /* for 'c' */                     \
      REF (form_unknown),   /* for 's', 'S' */                \
      REF (form_unknown),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_unknown),   /* for 'm' */                     \
      REF (form_unknown),   /* for 'C' */                     \
      REF (form_unknown),   /* for 'A', 'a' */                \                                                                                          
      REF (form_unknown),       /* for 't' */                     \
      REF (form_unknown),       /* for 'j' */                     \
      REF (form_unknown),       /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    };

(1).识别到'h'后

      /* Process 'h' modifier.  There might another 'h' following.  */
    LABEL (mod_half):
      is_short = 1;
      JUMP (*++f, step3a_jumps);

5.step3b_jumps[31]---识别到第一个'l'修饰符之后使用

对比step2_jumps之后发现:

  • 原来'l'的位置从REF (mod_long)变为REF (mod_longlong),为了识别'll'的情况
  • 'h','L','q','z','Z','t','j'被置为REF (form_unknown),因为这些都是非法组合
    /* Step 3b: after processing first 'l' modifier.  */              \
    static JUMP_TABLE_TYPE step3b_jumps[31] =                     \
    {                                         \
      REF (form_unknown),                             \
      REF (form_unknown),   /* for ' ' */                     \
      REF (form_unknown),   /* for '+' */                     \
      REF (form_unknown),   /* for '-' */                     \
      REF (form_unknown),   /* for '<hash>' */                \
      REF (form_unknown),   /* for '0' */                     \
      REF (form_unknown),   /* for ''' */                    \
      REF (form_unknown),   /* for '*' */                     \
      REF (form_unknown),   /* for '1'...'9' */               \
      REF (form_unknown),   /* for '.' */                     \
      REF (form_unknown),   /* for 'h' */                     \
      REF (mod_longlong),   /* for 'l' */                     \
      REF (form_unknown),   /* for 'L', 'q' */                \
      REF (form_unknown),   /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_float),     /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_character), /* for 'c' */                     \
      REF (form_string),    /* for 's', 'S' */                \
      REF (form_pointer),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_strerror),  /* for 'm' */                     \
      REF (form_wcharacter),    /* for 'C' */                     \
      REF (form_floathex),  /* for 'A', 'a' */                \                                                                                          
      REF (form_unknown),       /* for 't' */                     \
      REF (form_unknown),       /* for 'j' */                     \
      REF (form_unknown),       /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    }

(1),识别到'l'之后

     /* Process 'l' modifier.  There might another 'l' following.  */
    LABEL (mod_long):
      is_long = 1;
      JUMP (*++f, step3b_jumps);

以下跳转表都定义在STEP4_TABLE宏中

6.step4_jumps[31]---识别具体类型时使用

这里同step3a_jumps对比:

  • 'h'位置从REF (mod_halfhalf)变为REF (form_unknown),表明'hh'识别完成
  • 还原了之前不能和'h'组合的类型

同step3b_jumps对比:

  • 'l'位置从REF (mod_longlong)变为REF (form_unknown),表明'll'识别完成
  • 还原了之前不能和'l'组合的类型

总的来说,这里就是识别所有可能类型的

    /* Step 4: processing format specifier.  */                   \
    static JUMP_TABLE_TYPE step4_jumps[31] =                      \
    {                                         \
      REF (form_unknown),                             \
      REF (form_unknown),   /* for ' ' */                     \
      REF (form_unknown),   /* for '+' */                     \
      REF (form_unknown),   /* for '-' */                     \
      REF (form_unknown),   /* for '<hash>' */                \
      REF (form_unknown),   /* for '0' */                     \
      REF (form_unknown),   /* for ''' */                    \
      REF (form_unknown),   /* for '*' */                     \
      REF (form_unknown),   /* for '1'...'9' */               \
      REF (form_unknown),   /* for '.' */                     \
      REF (form_unknown),   /* for 'h' */                     \
      REF (form_unknown),   /* for 'l' */                     \
      REF (form_unknown),   /* for 'L', 'q' */                \
      REF (form_unknown),   /* for 'z', 'Z' */                \
      REF (form_percent),   /* for '%' */                     \
      REF (form_integer),   /* for 'd', 'i' */                \
      REF (form_unsigned),  /* for 'u' */                     \
      REF (form_octal),     /* for 'o' */                     \
      REF (form_hexa),      /* for 'X', 'x' */                \
      REF (form_float),     /* for 'E', 'e', 'F', 'f', 'G', 'g' */        \
      REF (form_character), /* for 'c' */                     \
      REF (form_string),    /* for 's', 'S' */                \
      REF (form_pointer),   /* for 'p' */                     \
      REF (form_number),    /* for 'n' */                     \
      REF (form_strerror),  /* for 'm' */                     \
      REF (form_wcharacter),    /* for 'C' */                     \
      REF (form_floathex),  /* for 'A', 'a' */                \                                                                                          
      REF (form_unknown),       /* for 't' */                     \
      REF (form_unknown),       /* for 'j' */                     \
      REF (form_unknown),       /* for 'I' */                     \
      REF (form_binary),    /* for 'B', 'b' */                \
    }

(1).识别到'hh'之后

      /* Process 'hh' modifier.  */
    LABEL (mod_halfhalf):
      is_short = 0;
      is_char = 1;
      JUMP (*++f, step4_jumps);

(2).识别到'L'、‘q’、'll'之后

      /* Process 'L', 'q', or 'll' modifier.  No other modifier is
     allowed to follow.  */
    LABEL (mod_longlong):
      is_long_double = 1;
      is_long = 1;
      JUMP (*++f, step4_jumps);

(3).识别到'z'之后

    LABEL (mod_size_t):
      is_long_double = sizeof (size_t) > sizeof (unsigned long int);
      is_long = sizeof (size_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

(4).识别到't'之后

    LABEL (mod_ptrdiff_t):
      is_long_double = sizeof (ptrdiff_t) > sizeof (unsigned long int);
      is_long = sizeof (ptrdiff_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

(5).识别到'j'之后

    LABEL (mod_intmax_t):
      is_long_double = sizeof (intmax_t) > sizeof (unsigned long int);
      is_long = sizeof (intmax_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

(6).识别specs中的每一个特定类型时使用

  /* Now walk through all format specifiers and process them.  */
  for (; (size_t) nspecs_done < nspecs; ++nspecs_done)
    {
      STEP4_TABLE;
      ...
      CHAR_T spec = specs[nspecs_done].info.spec;
      /* Process format specifiers.  */
      while (1)
    {
    ...
      JUMP (spec, step4_jumps);
     }
 }    

总结

跳转表的核心任务是解析

format占位符的一般形式:%[flags][width][.precision][length]specifier

通过使用五张跳转表(3a与3b处理两种情况,看做一张),对应识别的五个字段,后一个字段相比前一个字段,可跳转范围减少,对应限制只有合法的组合才能出现,一旦出现异常模式则直接跳转到REF (form_unknown)。

通过使用跳转表机制,将识别过程进行了拆分,使得逻辑更加清晰,每个阶段每张表任务不同,也不会造成干扰,值得我们学习借鉴。