Small. Fast. Reliable.
Choose any three.
SQLite的内置printf()

1.概述

SQLite包含其自己的字符串格式化例程“ printf()”的实现,可通过以下接口访问:

SQLite在内部也使用相同的核心字符串格式化程序。

1.1。好处

为什么SQLite具有自己的私有内置printf()实现?为什么不使用标准C库中的printf()实现?几个原因:

  1. 通过使用其自己的内置实现,SQLite保证所有平台和所有LOCALE上的输出都是相同的。这对于一致性和测试很重要。如果一台机器给出的答案为“ 5.25e + 08”,而另一台给出的答案为“ 5.250e + 008”,则将是有问题的。这两个答案都是正确的,但是当SQLite总是给出相同的答案时会更好。

  2. 我们知道无法使用标准库printf()C接口来实现SQLite的printf()SQL函数功能。但是,内置的printf()实现可以轻松地适应该任务。

  3. SQLite中的printf()支持新的非标准替换类型(%q%Q%w%z),这些替换类型在内部对SQLite以及使用SQLite的应用程序均有用。通常无法以这种方式扩展标准库的printf()s。

  4. 通过sqlite3_mprintf()sqlite3_vmprintf()接口,内置的printf()实现支持将任意长度的字符串呈现到从sqlite3_malloc64()获得的内存缓冲区中的功能。与尝试预先计算结果字符串的大小上限,分配适当大小的缓冲区然后调用snprintf()相比,这是更安全且更不易出错。

  5. 特定于SQLite的printf()支持称为“ alternate-form-2”标志的新标志(!)。alter-form-2标志以微妙的方式更改了浮点转换的处理,因此输出始终是SQL兼容的浮点数文本表示形式-使用标准库printf( )。对于字符串替换,alternate-form-2标志使宽度和精度以字符而不是字节进行测量,从而简化了对包含多字节UTF8字符的字符串的处理。

  6. 内置的SQLite具有诸如SQLITE_PRINTF_PRECISION_LIMIT之类的编译时选项,可为将printf()功能暴露给不受信任用户的应用程序提供防御拒绝服务攻击的保护。

  7. 使用内置的printf()实现意味着SQLite对主机环境的依赖性减少了一个,从而使其具有更高的可移植性。

1.2。缺点

公平地说,具有内置的printf()实现也有一些缺点。以机智:

  1. 内置的printf()实现使用额外的代码空间(在带有-O的GCC 5.4上约为7800字节)。

  2. 内置printf()的浮点到文本转换子函数的精度限制为16位有效数字,如果为“!”,则限制为26位有效数字。使用alter-form-2标志。每个IEEE-754双精度数都可以精确地表示为十进制浮点值,但是某些双精度数需要超过16或26个有效数字。

  3. 内置snprintf()实现中的缓冲区指针和缓冲区大小参数的顺序与标准库实现中使用的顺序相反。

  4. 内置的printf()实现不处理posix位置引用修饰符,该修饰符使printf()的参数顺序与%替换顺序不同。在内置的printf()中,参数的顺序必须与%替换的顺序完全匹配。

尽管存在缺点,但开发人员认为,在SQLite中具有内置的printf()实现是绝对有利的。

2.格式化细节

printf()的格式字符串是生成的字符串的模板。只要格式字符串中出现“%”字符,就会进行替换。“%”后跟一个或多个其他描述替换的字符。每个替换具有以下格式:

[flags] [width] [ 精度] [长度]类型

所有替换均以单个“%”开头,并以单个类型字符结尾。替代的其他元素是可选的。

要在输出中包含单个“%”字符,请在模板中放置两个连续的“%”字符。

2.1。替代类型

下表显示了SQLite支持的替换类型:

替代类型意义
连续两个“%”字符在输出中转换为单个“%”,而不替换任何值。
d,我 该参数是一个带符号的整数,以十进制显示。
ü 该参数是一个无符号整数,以十进制显示。
F 该参数是一个以十进制显示的双精度数。
e,E 该参数是一个双精度指数,以指数表示法显示。根据类型,指数字符为“ e”或“ E”。
该参数是一个double值,以常规十进制表示法显示,或者如果指数不接近零,则以指数表示法显示。
x,X 参数是一个以十六进制显示的整数。小写的十六进制用于%x,大写的十六进制用于%X
Ø 该参数是一个整数,以八进制显示。
s,z 该参数是一个以零结尾的字符串。对于C语言界面中的%z类型,在将字符串复制到输出中之后,将在字符串上调用sqlite3_free()。对于SQL printf()函数,%s和%z替换是相同的。

%s替换是通用的,但%z替换是SQLite的增强功能,在其他printf()实现中找不到。
C 对于C语言接口,参数是一个整数,它被解释为字符。对于printf()SQL函数,自变量是一个字符串,将从中提取并显示第一个字符。
p 参数是一个显示为十六进制地址的指针。由于SQL语言没有指针的概念,因此printf()SQL函数的%p替代项类似于%x。
ñ 参数是一个指向整数的指针。此替代类型不显示任何内容。而是,参数所指向的整数将由%n左侧所有格式符号产生的字符串中的字符数覆盖。
参数是一个以零结尾的字符串。该字符串打印时,所有单引号(')字符均应加倍,以便该字符串可以安全地出现在SQL字符串文字中。%Q替换类型还将单引号放在替换字符串的两端。

如果%Q的参数为空指针,则输出为未加引号的“ NULL”。换句话说,空指针生成SQL NULL,非空指针生成有效的SQL字符串文字。如果%q的参数为空指针,则不生成任何输出。因此,%q的空指针与空字符串相同。

对于这些替换,精度是从参数获取的字节数或字符数,而不是写入输出的字节数或字符数。

%q和%Q替换是SQLite的增强功能,在大多数其他printf()实现中都找不到。
w 此替换的工作方式与%q相似,不同之处在于它会将所有双引号字符(“)而不是单引号加倍,从而使结果适合在SQL语句中与双引号标识符名称一起使用。

%w替换是SQLite的增强功能,在其他大多数printf()实现中均未找到。

2.2。可选长度字段

参数值的长度可以由替换类型字母之前的一个或多个字母指定。在SQLite中,长度仅与整数类型有关。对于始终使用64位值的printf()SQL函数,将忽略该长度 。下表显示了SQLite允许的长度说明符:

长度说明符 意义
(默认) “ int”或“ unsigned int”。在所有现代系统上均为32位。
“ long int”或“ long unsigned int”。在所有现代系统上也是32位。
“ long long int”或“ long long unsigned”或“ sqlite3_int64”或“ sqlite3_uint64”值。在所有现代系统上,这些都是64位整数。

只有“ ll”长度修饰符才对SQLite有所不同。而且仅在使用C语言界面时有所不同。

2.3。可选宽度字段

width字段指定输出中替换值的最小宽度。如果写入输出的字符串或数字短于宽度,则将填充该值。默认情况下,填充位于左侧(该值右对齐)。如果使用“-”标志,则填充在右侧,并且该值左对齐。

默认情况下,宽度以字节为单位。但是,如果“!” 标记存在,则宽度以字符为单位。这仅对多字节utf-8字符有所不同,并且仅在字符串替换时出现。

如果宽度是单个“ *”字符而不是数字,则实际的宽度值将从参数列表中读取为整数。如果读取的值为负,则将绝对值用于宽度,并且将值左对齐,就好像存在“-”标志一样。

如果要替换的值大于宽度,则将完整值添加到输出中。换句话说,宽度是该值在输出中呈现时的最小宽度。

2.4。可选精度字段

精度字段(如果存在)必须遵循由单个“。”分隔的宽度。特点。如果没有宽度,则为“。” 引入精度的位置紧跟在标志(如果有)或初始“%”之后。

对于字符串替换(%s,%z,%q,%Q或%w),精度是自变量使用的字节数或字符数。默认情况下,该数字为字节,但如果为“!”,则为字符。标志存在。如果没有精度,则将替换整个字符串。示例:“%.3s”替换参数字符串的前3个字节。“%!. 3s”替换参数字符串的前三个字符。

对于整数替换(%d,%i,%x,%X,%o和%p),精度指定要显示的最小位数。如果需要,可以添加前导零,以将输出扩展到最小位数。

对于浮点替换(%e,%E,%f,%g,%G),精度指定要显示在小数点右边的位数。

对于字符替换(%c),精度N大于1会使字符重复N次。这是仅在SQLite中找到的非标准扩展。

2.5。选项标志字段

标志由零个或多个字符组成,这些字符紧随引入替换的“%”之后。各种标志及其含义如下:

旗帜 意义
-- 左对齐输出中的值。默认值为右对齐。如果宽度为零或小于要替换的值的长度,则不存在填充,并且“-”标志为空。
+ 对于带符号的数字替换,请在正数之前添加“ +”号。无论标志设置如何,始终在负数之前出现“-”符号。
(空间) 对于带符号的数字替换,请在正数之前加一个空格。
0 如有必要,可以在数字替换之前添加尽可能多的“ 0”字符,以将值扩展到指定的宽度。如果省略width字段,则此标志为空。
这是“ alternate-form-1”标志。对于%g和%G替换,这将导致删除尾随零。此标志强制所有浮点替换出现一个小数点。对于%o,%x和%X替换,alternate-form-1标志使值分别以“ 0”,“ 0x”或“ 0X”开头。
该标志使逗号分隔符被添加到%d和%i替换的输出中,从左数每3位之间。这可以帮助人们更轻松地识别大整数值的大小。例如,值2147483647将使用“%d”呈现为“ 2147483647”,但将显示为带有“%d”的“ 2,147,483,647”。此标志是非标准扩展名。
这是“ alternate-form-2标志。对于字符串替换,此标志使宽度和精度以字符而不是字节来理解。对于浮点替换,alternate-form-2标志增加了最大数目。从16到26的有效数字显示,强制显示小数点,并导致小数点后至少出现一位数字

Alternate-form-2标志是非标准扩展,在其他printf()实现中均未出现, 据我们所知。

3.实施和历史

核心字符串格式化例程是在printf.c源文件中找到的sqlite3VXPrintf()函数 。所有各种接口都调用(有时是间接调用)这一核心功能。sqlite3VXPrintf()函数最初是由SQLite的第一作者(Hipp)于1980年代末毕业于杜克大学(Duke University)时编写的代码。希普(Hipp)将此printf()实现保留在自己的工具箱中,直到2000年开始使用SQLite为止。该代码已在2000-10-08合并到 SQLite 1.0.9版的SQLite源代码树中。

化石版本控制系统使用从SQLite的printf()函数实现的早期版本获得了它自己的printf()实现,但是这两个实现都因为分歧。

The reason that the sqlite3_snprintf() has its buffer pointer and buffer size arguments reversed from what is found in the standard library snprintf() routine is because there was no snprintf() routine in the standard C library when Hipp was first implementing his version, and he chose a different order than the designers of the standard C library.