【问题标题】:passing unsigned char array to string functions将无符号字符数组传递给字符串函数
【发布时间】:2014-09-06 04:46:10
【问题描述】:

假设我有一些 utf8 编码的字符串。在其中,单词使用";" 分隔。 但是这个字符串中的每个字符(";" 除外)都有 utf8 值 >128。 假设我将此字符串存储在 unsigned char 数组中:

unsigned char buff[]="someutf8string;separated;with;";

将这个buff 传递给strtok 函数是否安全? (如果我只想使用";" 符号提取单词)。

我担心strtok(或strcpy)期望char指针,但在我的 字符串某些值的值 > 128。 那么这种行为是否已定义?

【问题讨论】:

  • strtok 是独立于语言环境的,所以它不应该有任何你想要的问题。
  • @AntonH:是的,但是例如 strcpy 呢?
  • strcpy 查找空终止符,并且 UTF-8 编码没有 0 字符,AFAIK。所以这也应该不是问题。但是,如果您愿意,请稍等一下,其他人会过来确认或反驳我所说的。这里有一些信息:java-samples.com/showtutorial.php?tutorialid=806
  • UTF-8 确实有一个 0 字符 - 与 ASCII 相同的 0 字符,因为 ASCII 是 UTF-8 的子集 - Unicode U+0000,编码为字节八位字节 0x00 就像在 ASCII 中一样。
  • @AntonH:空字节是U+0000的UTF-8编码。它确实有,但它的目的没有改变,所以大多数寻找零字节的字符串函数都可以使用 UTF-8(并且空字节永远不会在 UTF-8 中作为另一个字符的一部分出现)。

标签: c utf-8


【解决方案1】:

根据 C11 标准(ISO/IEC 9899:2011 §7.24.1 字符串处理约定,¶3,已添加重点):

对于本条中的所有功能,每个字符都应为 解释为好像它具有 unsigned char 类型(因此每个 可能的对象表示是有效的并且具有不同的值)。

注意:C99 标准中没有此段落。

所以我看不出有什么问题。

【讨论】:

  • 发明 UTF8 编码方案时,目标之一是允许 C 字符串库例程安全地处理它们,即使例程不了解 Unicode。特别是,您可以安全地复制它们、比较它们、排序它们,并按原样使用 ascii-7-subset 字符。 (摘自丹尼斯·里奇的论文?)
  • 设计 UTF8 的是 Ken Thompson,而不是 Dennis Ritchie。这是关于设计目标的备忘录:cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt
  • 该子句是说字符被读取为(unsigned char)*p,还是*(unsigned char *)p? (对于非 2 的补码负值,这些是不同的)
  • @MattMcNabb 我认为取消引用后的演员阵容最有意义。两者之间的细微差别有什么让您特别关注的地方吗?
  • @ChronoKitsune 我同意这更有意义,但是如果它是真的,那么如果'e' 是负数,unsigned char buf[] = "e"; strchr( (char *)buf, 'e' ) 可能会失败。 'e' 的值在存储在 buf 时会转换为 unsigned char,但将其别名回 signed char 可能会导致与 'e' 不同的字符。
【解决方案2】:

不,它不安全——但是如果它编译它几乎肯定会按预期工作。

unsigned char buff[]="someutf8string;separated;with;";

这很好;该标准特别允许使用字符串字面量初始化字符类型的数组(包括unsigned char)。字符串文字的连续字节初始化数组的元素。

strtok(buff, ";")

这是一个约束冲突,需要编译时诊断。 (这与 C 标准所说的某些东西是非法的差不多。)

strok 的第一个参数的类型为 char*,但您传递的参数类型为 unsigned char*。这两种指针类型不兼容,它们之间没有隐式转换。如果程序包含这样的调用(例如,gcc -std=c99 -pedantic-errors 确实会拒绝它),符合标准的编译器可能会拒绝您的程序。

许多 C 编译器在严格执行标准要求方面有些松懈。在许多情况下,编译器会针对包含违反约束的代码发出警告——这是完全有效的。但是,一旦编译器诊断出违反约束并继续生成可执行文件,该可执行文件的行为就不是由 C 标准定义的。

据我所知,任何不拒绝此调用的实际编译器都会生成行为符合您预期的代码。指针类型char*unsigned char* 几乎可以肯定具有相同的表示形式并以与参数相同的方式传递,并且类型charunsigned char 明确要求对非负值具有相同的表示形式。即使对于超过CHAR_MAX 的值,例如您正在使用的值,编译器也必须不遗余力地生成行为不端的代码。在不使用 2 补码作为有符号整数的系统上,您可能会遇到问题,但您不太可能遇到这样的系统。

如果您添加显式转换:

strtok((char*)buff, ";")

删除约束违规并可能会消除任何警告 - 但行为仍然严格未定义。

不过,在实践中,大多数编译器都试图将 charsigned charunsigned char 几乎互换处理,部分原因是为了迎合像您这样的代码,部分原因是他们必须竭尽全力做其他事情。

【讨论】:

  • @userq:不,没有矛盾。 违反约束是必须由编译器诊断的错误。 未定义的行为是编译器不需要检测的错误。
  • @userq:如果 C 程序违反约束,编译器必须诊断它,并可能拒绝它。如果它不拒绝它,它的行为是不确定的。强制转换会删除约束违规,但不会删除 UB。 “如果用户已经将她的字符串存储在一个无符号字符数组中,如果她想将字符串函数应用于这个缓冲区/字符串,你推荐什么解决方案?”——这是一个好问题。 C 似乎不能保证 charunsigned char 对象是可互换的,但它似乎仍然假设它们是。
  • 普通char是常用签名; UTF-8 需要无符号的 8 位数量。恕我直言,C 在调和它们方面做得不好且始终如一。使用演员表:strtok((char*)buff, ";") 可能是最好的方法;即使语言标准不保证它几乎肯定会起作用。
猜你喜欢
  • 1970-01-01
  • 2016-12-08
  • 2015-09-09
  • 1970-01-01
  • 1970-01-01
  • 2022-11-26
相关资源
最近更新 更多