Строка повреждена после перекодирования

Для описания я привожу минимальное воспроизведение следующего кода:

#include <bits/stdc++.h>
#include <iostream>
#include <regex>
#include <string>
#include <string>
#include <Windows.h>

// GBK 转 UTF-8
std::string GBKToUTF8(const std::string& gbkStr) {
    // 1. 先将 GBK 转换为宽字符(UTF-16)// Convert GBK to wide characters first (UTF-16)
    int len = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, nullptr, 0);
    std::wstring wstr(len, 0);
    MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, &wstr[0], len);

    // 2. 将宽字符(UTF-16)转换为 UTF-8 // Convert wide characters (UTF-16) to UTF-8
    len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    std::string utf8Str(len, 0);
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &utf8Str[0], len, nullptr, nullptr);

    return utf8Str;
}

int main() {
    // 示例身份证号,长度为18 // Example ID number, length 18
    std::string id_number = GBKToUTF8("610702199404261983");  
    // 检查字符串长度 // Check string length
    std::cout << "Length before: " << id_number.length() << "\n"
        << id_number << std::endl;

    // 正则表达式 // Regular expression
    const std::regex id_number_pattern18("^([1-6][1-9]|50)\\d{4}(18|19|20)\\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");

    // 进行匹配 // Make a match
    if (std::regex_match(id_number, id_number_pattern18)) {
        std::cout << "Match successful!" << std::endl;
    } else {
        std::cout << "Match failed!" << std::endl;
    }

    return 0;
}

Проблема теперь в том, что когда строка id_number перекодируется в UTF-8, длина меняется с 18 на 19. Кроме того, регулярное выражение больше не соответствует строке правильно (ее можно правильно сопоставить, если она не перекодирована).

Я подозреваю, что строка была перекодирована и добавлены какие-то невидимые символы, но я не знаю, как это исправить.

Вот несколько скриншотов отладки VS2022 (ISO C++17) для справки (конечно, скриншоты не из минимального кода воспроизведения, но их следует хорошо понимать):

Перед перекодированием

После перекодирования

Я не знаю, как это сделать на данный момент, или я хотел бы предоставить решение и описание того, как возникает проблема.

Shold, вероятно, будет std::string utf8Str(len - 1, 0);, так как len, кажется, считается финальным '\0' для C-строки.

Jarod42 15.08.2024 16:41

Вы запрашиваете API для преобразования последнего NUL-символа в ваших строках, поэтому ваши выходные строки теперь содержат два, а не один NUL-терминатор: один, предоставляемый std::[w]string, и один в контролируемой последовательности. Решение простое: передайте size() вместо -1 в качестве длины. Это также менее затратно.

IInspectable 15.08.2024 16:42

Из документации: «Если этот параметр равен -1, функция обрабатывает всю входную строку, включая завершающий нулевой символ. Следовательно, результирующая строка символов имеет завершающий нулевой символ, а длина, возвращаемая функцией, включает этот символ».

Raymond Chen 15.08.2024 16:42

Я не уверен в логике здесь. Почему вы используете кодовую страницу ANSI?

Dúthomhas 15.08.2024 16:48

@Dúthomhas Исходная строка находится в формате CP 936, который для этого приложения является 8-битной кодовой страницей по умолчанию.

Raymond Chen 15.08.2024 17:59

@RaymondChen Да, но в названии функции указано, что она принимает строку упрощенного китайского GBK.

Dúthomhas 15.08.2024 18:05

@Dúthomhas Думаю, мы согласны. Упрощенный китайский GBK — это 8-битная кодировка, которую Windows называет CP 936.

Raymond Chen 15.08.2024 18:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что вы просите MultiByteToWideChar() и WideCharToMultiByte() включить место для явного NUL-терминатора в длину, которую они возвращают:

[в] cbMultiByte

Размер строки в байтах, указанной параметром lpMultiByteStr. В качестве альтернативы этому параметру можно установить значение -1, если строка завершается нулем. Обратите внимание: если cbMultiByte равен 0, функция завершится неудачно.

Если этот параметр равен -1, функция обрабатывает всю входную строку, включая завершающий нулевой символ. Таким образом, результирующая строка Юникода имеет завершающий нулевой символ, а длина, возвращаемая функцией, включает этот символ.

Вы включаете это дополнительное пространство при выделении памяти для std::wstring и std::string. Но, в отличие от строк C, строки C++ не завершаются нулем. Они могут содержать встроенные NUL-символы, которые включены в их size, и иметь неявный NUL-терминатор, который НЕ включен в их size.

Таким образом, вы не должны рассматривать строки C++ как завершающиеся нулем. Не запрашивайте у API место для терминатора NUL. Вместо этого используйте фактические размеры строк, например:

std::string GBKToUTF8(const std::string& gbkStr) {
    // 1. 先将 GBK 转换为宽字符(UTF-16)
    int len = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), gbkStr.size(), nullptr, 0);
                                                          // ^^^^^^^^^^^^^
    std::wstring wstr(len, 0);
    MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), gbkStr.size(), &wstr[0], len);
                                                // ^^^^^^^^^^^^^

    // 2. 将宽字符(UTF-16)转换为 UTF-8
    len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), nullptr, 0, nullptr, nullptr);
                                                     // ^^^^^^^^^^^
    std::string utf8Str(len, 0);
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), &utf8Str[0], len, nullptr, nullptr);
                                               // ^^^^^^^^^^^

    return utf8Str;
}

«Альтернатива» — единственное правильное решение.

IInspectable 15.08.2024 16:49

@IInspectable согласился. Я удалил другой пример

Remy Lebeau 15.08.2024 17:01

Так лучше, спасибо. Поскольку строки C++ могут содержать встроенные символы NUL, нет причин избегать поддержки этого. Хотя это, как правило, не очень хорошая идея, символы NUL могут случайно оказаться в контролируемой последовательности (как показано в вопросе). Благодаря поддержке встроенных символов NUL реализация больше не маскирует ошибки в других частях кода.

IInspectable 15.08.2024 17:14

Другие вопросы по теме