icu库使用笔记

编译icu库

下载icu库的源码包:
https://github.com/unicode-org/icu/releases
需要注意75版本之后全部强制使用c++17标准,所以如果项目中如果想用75版本之后的新版本的话就要将项目的C++语言升级到c++17之后

参考编译手册进行编译:
https://unicode-org.github.io/icu/userguide/icu4c/build

cd source
# 如果需要调试的话可以加上选项--enable-debug
./configure --prefix=`pwd`/libicu --enable-static --disable-shared --with-data-packaging=static
make -j 20 install

运行一个小的demo(保存为test_icu.cpp):

#include <iostream>
#include <unicode/regex.h> // ICU正则表达式头文件
#include <unicode/unistr.h> // ICU Unicode字符串头文件

int main() {
    // 初始化ICU库
    UErrorCode status = U_ZERO_ERROR;

    // 定义要匹配的字符串
    icu::UnicodeString text("Hello, 世界! 12345");

    // 定义正则表达式
    icu::UnicodeString pattern("\\p{L}+"); // 匹配一个或多个字母(包括Unicode字母)
    icu::RegexMatcher matcher(pattern, text, 0, status);

    // 检查正则表达式是否编译成功
    if (U_FAILURE(status)) {
        std::cerr << "正则表达式编译失败: " << u_errorName(status) << std::endl;
        return 1;
    }

    // 进行匹配
    while (matcher.find(status)) {
        if (U_FAILURE(status)) {
            std::cerr << "匹配过程中发生错误: " << u_errorName(status) << std::endl;
            break;
        }

        // 获取匹配到的子串
        icu::UnicodeString match = matcher.group(status);
        if (U_FAILURE(status)) {
            std::cerr << "获取匹配结果失败: " << u_errorName(status) << std::endl;
            break;
        }

        // 输出匹配结果
        std::string matchStr;
        match.toUTF8String(matchStr);
        std::cout << "匹配到: " << matchStr << std::endl;
    }

    return 0;
}

编译运行:

g++ test_icu.cpp -I /path/to/libicu/include -g -c -o test_icu.o
g++ test_icu.o /path/to/libicu/lib/libicui18n.a /path/to/libicu/lib/libicuuc.a /path/to/libicu/lib/libicudata.a -ldl -pthread -g -o test_icu
./test_icu

编译运行问题

在使用ICU库时,想写一个简单案例来验证下功能是否满足要求,但是编译链接后运行会出core:

core的位置很奇怪,在一个icu内部函数umtx_initImplPreInit的结束位置,没有发现对应的代码行。
实在没有办法,就通过gdb来单步调试,发现是在调用umtx_initImplPreInit中的std::call_once这一行代码时抛出了system_error异常(所以gdb显示core在umtx_initImplPreInit的结束位置)

std:call_once这个函数是c++标准库中的函数,它会根据参数中给定的flag来判断和标记是否要指定给定的callable函数对象。

了解了call_once的用法后,在callable对象对应的函数体的第一行加断点继续调试,发现并没有经过此断点就core了,所以问题还是发生在call_once内部的还没调用到此函数的某个位置。

然后继续从call_once内部单步调试,定位到__ghtread_once这个函数会抛出异常。

__ghtread_once是做什么用的?由于碰到知识盲点了,就求助deepseek:__gthread_once函数抛出异常是什么问题?

解答非常详尽,给出了多种可能性的答案,从经验判断应该是最后一种可能性导致的:

__gthread_once 依赖于 pthread_once,但不同版本的 GCC 可能有不同的实现。如果你在不同的 C 运行库(libc)或 libpthread 版本之间存在兼容性问题,可能会导致 __gthread_once 失败。

检查 GCC 线程支持的方法:
gcc -dM -E - < /dev/null | grep THREADS
如果输出中没有 #define _REENTRANT 或 #define _POSIX_THREADS,说明你的 GCC 没有启用 POSIX 线程支持。

按照这个方式检查后得到的结果是#define STDC_NO_THREADS 1, 说明确实是这样的情况,解决方法就是在g++编译命令上加上参数-pthread
至此问题解决,没有发生core了。

虽然问题解决了,但是还需要思考总结下这个过程:

  1. 在尝试的过程中一直以为是icu库编译的问题,换了多个版本还是有问题
  2. 在查看报错信息时也无法得到有效信息
  3. 对于最终的问题解决还是通过deepseek的帮助
  4. 对于这种非常隐蔽的问题排查,需要有足够的信息才能通过AI工具来得到解决,要不然也是浪费时间

utf16编码

utf16编码是将unicode的32位码位值编码为2字节或者4字节的数据。当前的所有unicode字符不超过2^21个,所以最多用21位bit就能表示。

  1. 对于码位值在0x0000~0xFFFF的unicode字符,因为它的值用2字节(16位)表示就足够了,所以使用2字节存储。
  2. 对于码位值在0x10000~0x10FFFF的unicode字符,表示它的话需要超过2字节,那就要用4字节存储,分成高代理的2字节和低代理的2字节
    编码方法如下:
    1. 将2进制的最高位(第21位)的1置0,剩下的20位中前10位2进制数前面拼接上0100,凑到16位,得到高代理的2字节
    2. 前面处理步骤中20位中后10位2进制数前面拼接上1100,凑到16位,得到低代理的2字节
    3. 高代理2字节拼接上低代理2字节得到最后的4字节编码

对于这种方式的编码文件,解析程序可以快速识别出每个连续2字节是一个非代理代码单元(0x0000~0xFFFF),高代理代码单元或者是低代理代码单元

utf8编码

utf8编码是将unicode的32位码位值编码为1~4字节的数据。

  1. 对于0x0~0x7F的数据,编码为1个字节:
    0 x x x x x x x
  2. 对于0x80~0x7FF的数据,编码为2个字节:
    1 1 0 x x x x x | 1 0 x x x x x x
  3. 对于0x800~0xFFFF编码为3个字节:
    1 1 1 0 x x x x | 1 0 x x x x x x | 1 0 x x x x x x
  4. 对于0x10000~0x1FFFF编码为4个字节:
    1 1 1 1 0 x x x | 1 0 x x x x x x | 1 0 x x x x x x | 1 0 x x x x x x

utf8编码转utf16编码函数

U_CAPI int32_t U_EXPORT2 u_strFromUTF8(
    UChar *dest,             // 目标 UChar* 缓冲区(UTF-16)
    int32_t destCapacity,    // 目标缓冲区大小,UChar的个数
    int32_t *pDestLength,    // 转换后的UChar个数
    const char *src,         // 源 UTF-8 字符串
    int32_t srcLength,       // utf8编码的源字符串的字节长度(-1 表示以 `\0` 结尾)
    UErrorCode *pErrorCode   // 错误状态码
);
例如😊这个字符:
- UNICODE值为0x0001F60A
- UTF8的编码数据为0xF0,0x9F,0x98,0x8A
- UTF16编码为0xD83D,0xDE0A
- UTF16-BE编码为0xD8,0x3D,0xDE,0x0A
- UTF16-LE编码为0x3D,0xD8,0x0A,0xDE
保存一个😊字符的UTF-8编码的数据长度为4字节,UTF16编码的数据长度为4字节,2 UChar
上述函数的destCapacity表示dest的UChar长度,srcLength表示src的字节长度(也就是4)
posted @ 2025-02-20 14:12  bug批发零售  阅读(18)  评论(0编辑  收藏  举报