如何更改 wchar.h 以使 wchar_t 与 wint_t 类型相同?

Posted

技术标签:

【中文标题】如何更改 wchar.h 以使 wchar_t 与 wint_t 类型相同?【英文标题】:How to change wchar.h to make wchar_t the same type as wint_t? 【发布时间】:2017-03-28 20:26:37 【问题描述】:

wchar_t 定义在wchar.h

目前,如果开发者只想使用wchar_t,他们做不到 这没有从编译器获得类型转换警告。如果 wchar_t 将与wint_t 的类型相同,这对双方都有好处。 希望同时拥有wint_twchar_t 的开发人员 程序(例如,如果他们希望他们的代码不仅在 glibc) 可以做到这一点而不会收到编译器警告。开发商们 只想使用wchar_t(以避免使用wint_t和 显式类型转换)也可以做到这一点而不会收到编译器警告。 而且它不会带来任何不兼容或可移植性问题,除非如果只使用wchar_t 的代码将在使用原始wchar.h 的机器上编译,编译器将打印那些讨厌的警告(如果启用了-Wconversion),但是编译后的程序将以完全相同的方式运行。

C 标准 (9899:201x 7.29) 提到:

wchar_t 和 wint_t 可以是相同的整数类型。

另外,在 glibc 中,宽字符总是 ISO10646/Unicode/UCS-4,所以它们总是使用 4 个字节。因此,什么都没有 防止在 glibc 中使 wchar_twint_t 的类型相同。

但似乎 glibc 的开发者不想让wint_twchar_t 出于某种原因是同一类型。因此,我想更改的本地副本 wchar.h.

ISO10646/Unicode/UCS-4 使用 2^31 值作为扩展字符集 (MSB 未使用):

0xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx

注意,一个 4 字节的类型可以保存 2^31 额外值(MSB 为“1”):

1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx

任何这些额外的值都可以用来表示WEOF,因此一个 4字节类型可用于保存所有字符集WEOF

注意,使用修改后的wchar.h不需要重新编译glibc,因为 wint_t 可以有符号或无符号(因为 -10xffffffff 都有 MSB “1”,在任何表示中,并且由于 MSB 不用于 ISO10646/Unicode/UCS-4)。

wchar_t 的定义在wchar.h 的以下摘录中完成。 如何更改它以使wchar_twint_t 的类型相同?

#ifndef _WCHAR_H

#if !defined __need_mbstate_t && !defined __need_wint_t
# define _WCHAR_H 1
# include <features.h>
#endif

#ifdef _WCHAR_H
/* Get FILE definition.  */
# define __need___FILE
# if defined __USE_UNIX98 || defined __USE_XOPEN2K
#  define __need_FILE
# endif
# include <stdio.h>
/* Get va_list definition.  */
# define __need___va_list
# include <stdarg.h>

# include <bits/wchar.h>

/* Get size_t, wchar_t, wint_t and NULL from <stddef.h>.  */
# define __need_size_t
# define __need_wchar_t
# define __need_NULL
#endif
#if defined _WCHAR_H || defined __need_wint_t || !defined __WINT_TYPE__
# undef __need_wint_t
# define __need_wint_t
# include <stddef.h>

/* We try to get wint_t from <stddef.h>, but not all GCC versions define it
   there.  So define it ourselves if it remains undefined.  */
# ifndef _WINT_T
/* Integral type unchanged by default argument promotions that can
   hold any value corresponding to members of the extended character
   set, as well as at least one value that does not correspond to any
   member of the extended character set.  */
#  define _WINT_T
typedef unsigned int wint_t;
# else
/* Work around problems with the <stddef.h> file which doesn't put
   wint_t in the std namespace.  */
#  if defined __cplusplus && defined _GLIBCPP_USE_NAMESPACES \
      && defined __WINT_TYPE__
__BEGIN_NAMESPACE_STD
typedef __WINT_TYPE__ wint_t;
__END_NAMESPACE_STD
#  endif
# endif

/* Tell the caller that we provide correct C++ prototypes.  */
# if defined __cplusplus && __GNUC_PREREQ (4, 4)
#  define __CORRECT_ISO_CPP_WCHAR_H_PROTO
# endif
#endif

#if (defined _WCHAR_H || defined __need_mbstate_t) && !defined ____mbstate_t_defined
# define ____mbstate_t_defined  1
/* Conversion state information.  */
typedef struct

  int __count;
  union
  
# ifdef __WINT_TYPE__
    __WINT_TYPE__ __wch;
# else
    wint_t __wch;
# endif
    char __wchb[4];
   __value;        /* Value so far.  */
 __mbstate_t;
#endif
#undef __need_mbstate_t


/* The rest of the file is only used if used if __need_mbstate_t is not
   defined.  */
#ifdef _WCHAR_H

# ifndef __mbstate_t_defined
__BEGIN_NAMESPACE_C99
/* Public type.  */
typedef __mbstate_t mbstate_t;
__END_NAMESPACE_C99
#  define __mbstate_t_defined 1
# endif

#ifdef __USE_GNU
__USING_NAMESPACE_C99(mbstate_t)
#endif

#ifndef WCHAR_MIN
/* These constants might also be defined in <inttypes.h>.  */
# define WCHAR_MIN __WCHAR_MIN
# define WCHAR_MAX __WCHAR_MAX
#endif

#ifndef WEOF
# define WEOF (0xffffffffu)
#endif

/* For XPG4 compliance we have to define the stuff from <wctype.h> here
   as well.  */
#if defined __USE_XOPEN && !defined __USE_UNIX98
# include <wctype.h>
#endif


__BEGIN_DECLS

__BEGIN_NAMESPACE_STD
/* This incomplete type is defined in <time.h> but needed here because
   of `wcsftime'.  */
struct tm;
__END_NAMESPACE_STD
/* XXX We have to clean this up at some point.  Since tm is in the std
   namespace but wcsftime is in __c99 the type wouldn't be found
   without inserting it in the global namespace.  */
__USING_NAMESPACE_STD(tm)

【问题讨论】:

顺便说一句,如果 glibc 开发人员添加一些关于使用什么__need_wint_t__need_mbstate_t__WINT_T__WINT__TYPE 等的文档,那就不错了……我不能制作这个神秘代码的头部或尾部。 【参考方案1】:

请注意,引入 wint_t 是因为 wchar_t 在传递给 printf() 等时可能是受“默认提升”规则约束的类型。这很重要,例如,在调用printf() 时:

wchar_t wc = …;
printf("%lc", wc);

wc 的值可能会转换为wint_t。如果你正在编写像printf() 这样的函数,它需要使用来自&lt;stdarg.h&gt;va_arg() 宏,那么你应该使用wint_t 类型来获取值。

标准指出wint_t 可能与wchar_t 类型相同,但如果wchar_t 是(16 位)short(或unsigned short),wint_t 可能是(32-位)int。大致而言,wint_t 仅在 wchar_t 是 16 位类型时才重要。当然,完整的规则更复杂。例如,int 可能是 16 位类型 — 但这很少会成为问题。

ISO/IEC 9899:2011

7.29 扩展多字节和宽字符实用程序&lt;wchar.h&gt;

7.29.1 简介

¶1 头文件&lt;wchar.h&gt; 定义了四个宏,并声明了四种数据类型、一个标签和 很多功能。326)

2 声明的类型为wchar_tsize_t(均在7.19中描述);

mbstate_t

这是一个完整的对象类型,而不是可以保存转换状态的数组类型 在多字节字符序列和宽字符序列之间转换所需的信息 字符;

wint_t

这是一个整数类型,默认情况下不变,参数提升可以容纳任何 对应于扩展字符集成员的值,以及至少一个 与扩展字符集的任何成员都不对应的值(请参阅WEOF 下面);327)

326) 参见“未来图书馆方向”(7.31.16)。327)wchar_twint_t 可以是同一个整数输入。

§7.19 通用定义&lt;stddef.h&gt;

¶2 … 和

wchar_t

这是一个整数类型,其值范围可以代表所有不同的代码 支持的语言环境中指定的最大扩展字符集的成员;这 空字符的代码值为零。基本字符集的每个成员 当用作整数中的唯一字符时,其代码值应等于其值 如果实现未定义字符常量 __STDC_MB_MIGHT_NEQ_WC__.

请参阅Why the argument type of putchar(), fputc(), and putc() is not char 以了解引用 C 标准中的“默认提升”规则的地方。可能还有其他可用信息的问题。

【讨论】:

所以,总而言之,wint_t wchar_t 是 16 位数据类型时才相关?如果我们不想保持与 OS Windows 的向后兼容性(这是唯一使用 16 位 wint_t 的操作系统)并始终使用 wchar_t,我们可以直接在代码中使用一些 pragma 吗? (但要避免类型转换警告) 关于“wint_t 仅在 wchar_t 是 16 位数据类型时相关”,答案或多或少 - 我已经温和地更新了答案以讨论这一点。很抱歉,我对(当前)Windows 和(当前)Windows 编译器没有足够的经验,不知道是否有办法处理您在评论中概述的问题。【参考方案2】:

如果我们需要在使用-Wconversion编译器选项时避免类型转换警告,我们需要在所有库函数的原型中将wint_t更改为wchar_t,并放入'#define WEOF(-1)'到wchar.hwctype.h的开头

对于wchar.h,命令是:

sudo perl -i -pe 'print qq(#define WEOF (-1)\n) if $.==1; next unless /Copy SRC to DEST\./..eof; s/\bwint_t\b/wchar_t/g' /usr/include/wchar.h

对于wctype.h,命令是:

sudo perl -i -pe 'print qq(#define WEOF (-1)\n) if $.==1; next unless /Wide-character classification functions/..eof; s/\bwint_t\b/wchar_t/g' /usr/include/wctype.h

同样,如果您使用其他使用wint_t 的头文件,只需在这些头文件的原型中将wint_t 更改为wchar_t

解释如下。

一些 Unix 系统将 wchar_t 定义为 16 位类型,因此非常严格地遵循 Unicode。这个定义完全符合标准,但这也意味着要表示来自 Unicode 和 ISO 10646 的所有字符,必须使用 UTF-16 代理字符,这实际上是一种多宽字符编码。但是采用多宽字符编码与wchar_t 类型的目的相矛盾。

现在,为了数据交换而存活的唯一编码是UTF-8,它可以容纳的最大数据位数是31

1111110x    10xxxxxx    10xxxxxx    10xxxxxx    10xxxxxx    10xxxxxx

因此,您会看到,实际上没有必要将 wint_t 作为单独的类型(因为 4 字节(即 32 位)数据类型用于存储 Unicode 代码点反正)。也许它有一些“向后兼容”的应用程序或其他东西,但在新代码中它是没有意义的。再一次,因为它完全违背了使用宽字符的目的(而且现在无法处理 UTF-8 对使用宽字符毫无意义)。

注意,事实上的wint_t 无论如何都不会被使用。例如,请参阅man mbstowcs 中的示例。在那里,wchar_t 类型的变量被传递给iswlower() 和来自wctype.h 的其他函数,它们采用wint_t

【讨论】:

请注意,虽然 UTF-8 底层的编码方案可以扩展到处理多达 31 位(或者在一些不合理的假设下甚至更远),但实际上,Unicode 联盟已经定义 Unicode 是21 位代码集,最大代码点为 U+10FFFF,这意味着 4 个字节足以编码 UTF-8 中的任何 Unicode 代码点。选择的原因是 UTF-16 代理对最多可以编码 U+10FFFF 并且不能进一步编码。请参阅UTF-8, UTF-16, UTF-32 and BOM FAQ 了解更多信息。 @JonathanLeffler 好的,但是数据类型需要 4 个字节来在内部存储该 21 位代码点,因为 3 字节数据类型不实用。即使是这样,WEOF 仍然有空间,所以,无论如何,拥有wint_t 没有任何好处,反而会造成不便。 应该有一些编译器指令可以使wint_twchar_t 具有相同的类型,我正试图找出它是什么。但无法理解 wchar.h 代码... 是的,wint_t 是个麻烦事,但在逻辑上允许printf() 使用标准 C 编写是必要的。你说得对,现代机器没有 24 位整数类型——从历史上看,我相信有一些,但它们早于我使用计算机的日期,因此要存储 Unicode 代码点,您使用 32 位整数和 UTF-32(自从值将始终为正)。 @JonathanLeffler 我完全没有想到的是为什么 glibc 开发人员制作了 wint_twchar_t 不同的签名。如果他们都签名或都未签名 - 不会有任何警告,每个人都会很高兴。另外,您关于printf() 的观点我仍然不清楚。我觉得我已经接近理解这个烂摊子了......也许值得问一个单独的问题,比如“如何从头开始实现 printf()?” 对于wint_twchar_t 具有不同签名的基本原理,您必须咨询 GNU C 库 (glibc) 的设计者。虽然EOF 必须为负数(但不一定是-1,尽管这是迄今为止最常见的值),但WEOF 没有类似的要求。我还没有完成在printf() 中支持宽字符所需的所有步骤,所以我自己并不完全清楚所有的来龙去脉。我相信,SO上有一些居民已经实现了这样的代码;也许你可以设法让他们注意到你的问题。

以上是关于如何更改 wchar.h 以使 wchar_t 与 wint_t 类型相同?的主要内容,如果未能解决你的问题,请参考以下文章

如何初始化 wchar_t 变量?

用c语言如何连接两个中文字符串?

MultiByteToWideChar 和 WideCharToMultiByte 用法详解

c str to float

wcschr (Strings) – C 中文开发手册

error: No curses/termcap library found