Comment construisez-vous une std :: string avec un null incorporé?

88

Si je veux construire un std :: string avec une ligne comme:

std::string my_string("a\0b");

Là où je veux avoir trois caractères dans la chaîne résultante (a, null, b), je n'en reçois qu'un. Quelle est la syntaxe appropriée?

Facture
la source
4
Vous devrez être prudent avec cela. Si vous remplacez «b» par un caractère numérique, vous créerez silencieusement la mauvaise chaîne. Voir: stackoverflow.com/questions/10220401/…
David Stone

Réponses:

129

Depuis C ++ 14

nous avons pu créer littéralement std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Avant C ++ 14

Le problème est que le std::stringconstructeur qui prend a const char*suppose que l'entrée est une chaîne C. Les chaînes C sont \0terminées et ainsi l'analyse s'arrête quand elle atteint le \0caractère.

Pour compenser cela, vous devez utiliser le constructeur qui construit la chaîne à partir d'un tableau de caractères (pas une chaîne C). Cela prend deux paramètres - un pointeur vers le tableau et une longueur:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Remarque: C ++ std::stringn'est PAS \0 terminé (comme suggéré dans d'autres articles). Toutefois, vous pouvez extraire un pointeur vers un tampon interne qui contient une chaîne C avec la méthode c_str().

Consultez également la réponse de Doug T ci-dessous sur l'utilisation d'un vector<char>.

Consultez également RiaD pour une solution C ++ 14.

Martin York
la source
7
mise à jour: à partir de C ++ 11, les chaînes se terminent par un NULL. Cela étant dit, le message de Loki reste valable.
matthewaveryusa
14
@mna: Ils sont terminés par un nul en termes de stockage, mais pas dans le sens où ils sont terminés par un nul avec une terminaison nulle significative (c'est-à-dire avec une sémantique définissant la longueur de chaîne), qui est le sens habituel du terme.
Courses de légèreté en orbite
Bien expliqué. Merci.
Joma
22

Si vous effectuez une manipulation comme vous le feriez avec une chaîne de style c (tableau de caractères), envisagez d'utiliser

std::vector<char>

Vous avez plus de liberté pour le traiter comme un tableau de la même manière que vous traiteriez une chaîne c. Vous pouvez utiliser copy () pour copier dans une chaîne:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

et vous pouvez l'utiliser dans de nombreux endroits où vous pouvez utiliser des chaînes de caractères

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Naturellement, cependant, vous souffrez des mêmes problèmes que les c-strings. Vous pouvez oublier votre terminal nul ou écrire au-delà de l'espace alloué.

Doug T.
la source
Si vous dites essayer d'encoder des octets en chaîne (les octets grpc sont stockés sous forme de chaîne), utilisez la méthode vectorielle comme spécifié dans la réponse; pas la manière habituelle (voir ci-dessous) qui ne construira PAS la chaîne entière byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen
13

Je ne sais pas pourquoi vous voudriez faire une telle chose, mais essayez ceci:

std::string my_string("a\0b", 3);
17 sur 26
la source
1
Quelles sont vos préoccupations à ce sujet? Vous vous interrogez sur la nécessité de stocker "a \ 0b"? ou remettre en question l'utilisation d'une chaîne std :: string pour un tel stockage? Dans ce dernier cas, que proposez-vous comme alternative?
Anthony Cramp
3
@Constantin alors vous faites quelque chose de mal si vous stockez des données binaires sous forme de chaîne. C'est ce pour quoi vector<unsigned char>ou unsigned char *ont été inventés.
Mahmoud Al-Qudsi, le
2
Je suis tombé sur cela en essayant d'en savoir plus sur la sécurité des chaînes. Je voulais tester mon code pour m'assurer qu'il fonctionne toujours même s'il lit un caractère nul en lisant à partir d'un fichier / réseau ce qu'il s'attend à être des données textuelles. J'utilise std::stringpour indiquer que les données doivent être considérées comme du texte brut, mais je fais un travail de hachage et je veux m'assurer que tout fonctionne toujours avec des caractères nuls impliqués. Cela semble être une utilisation valide d'un littéral de chaîne avec un caractère nul incorporé.
David Stone
3
@DuckMaestro Non, ce n'est pas vrai. Un \0octet dans une chaîne UTF-8 ne peut être que NUL. Un caractère codé sur plusieurs octets ne contiendra jamais - \0ni aucun autre caractère ASCII d'ailleurs.
John Kugelman
1
Je suis tombé sur cela en essayant de provoquer un algorithme dans un cas de test. Il y a donc des raisons valables; bien que peu nombreux.
namezero
12

Quelles nouvelles fonctionnalités les littéraux définis par l'utilisateur ajoutent-ils à C ++? présente une réponse élégante: définir

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

alors vous pouvez créer votre chaîne de cette façon:

std::string my_string("a\0b"_s);

ou même ainsi:

auto my_string = "a\0b"_s;

Il y a une manière «à l'ancienne»:

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

alors vous pouvez définir

std::string my_string(S("a\0b"));
anonyme
la source
8

Ce qui suit fonctionnera ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Andrew Stein
la source
Vous devez utiliser des parenthèses au lieu des crochets.
jk.
5

Vous devrez être prudent avec cela. Si vous remplacez «b» par un caractère numérique, vous créerez silencieusement la mauvaise chaîne en utilisant la plupart des méthodes. Voir: Règles pour le caractère d'échappement des littéraux de chaîne C ++ .

Par exemple, j'ai laissé tomber cet extrait de code innocent au milieu d'un programme

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Voici ce que ce programme a produit pour moi:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

C'était ma première déclaration imprimée deux fois, plusieurs caractères non imprimables, suivis d'un saut de ligne, suivi de quelque chose dans la mémoire interne, que j'ai juste écrasé (puis imprimé, montrant qu'il a été écrasé). Le pire de tout, même compiler cela avec des avertissements gcc détaillés et détaillés ne m'a donné aucune indication que quelque chose n'allait pas, et l'exécution du programme via valgrind ne se plaignait pas des modèles d'accès à la mémoire incorrects. En d'autres termes, il est complètement indétectable par les outils modernes.

Vous pouvez obtenir le même problème avec le plus simple std::string("0", 100);, mais l'exemple ci-dessus est un peu plus délicat, et donc plus difficile à voir ce qui ne va pas.

Heureusement, C ++ 11 nous offre une bonne solution au problème en utilisant la syntaxe de la liste d'initialisation. Cela vous évite d'avoir à spécifier le nombre de caractères (ce que, comme je l'ai montré ci-dessus, vous ne pouvez pas faire correctement), et évite de combiner des nombres échappés. std::string str({'a', '\0', 'b'})est sans danger pour tout contenu de chaîne, contrairement aux versions qui prennent un tableau de charet une taille.

David Stone
la source
2
Dans le cadre de ma préparation pour ce post, j'ai soumis un rapport de bogue à gcc dans l'espoir qu'ils ajouteront un avertissement pour rendre cela un peu plus sûr: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone
4

En C ++ 14, vous pouvez maintenant utiliser des littéraux

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
RiaD
la source
1
et la deuxième ligne peut également être écrite, plus joliment à mon humble avis, commeauto s{"a\0b"s};
underscore_d
Bonne réponse Merci.
Joma
1

Mieux vaut utiliser std :: vector <char> si cette question n'est pas uniquement à des fins éducatives.

Harold Ekstrom
la source
1

La réponse d'anonym est excellente, mais il existe également une solution non macro en C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Avec cette fonction, RawString(/* literal */)produira la même chaîne que S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

De plus, il y a un problème avec la macro: l'expression n'est pas réellement une std::stringtelle qu'elle est écrite, et ne peut donc pas être utilisée par exemple pour une simple initialisation d'affectation:

std::string s = S("a\0b"); // ERROR!

... il peut donc être préférable d'utiliser:

#define std::string(s, sizeof s - 1)

Évidemment, vous ne devez utiliser que l'une ou l'autre solution dans votre projet et l'appeler comme vous le jugez approprié.

Kyle Strand
la source
-5

Je sais que cela fait longtemps que cette question est posée. Mais pour quiconque rencontre un problème similaire pourrait être intéressé par le code suivant.

CComBSTR(20,"mystring1\0mystring2\0")
Dil09
la source
Cette réponse est trop spécifique aux plates-formes Microsoft et ne répond pas à la question d'origine (qui posait sur std :: string).
Juin Rhodes
-8

Presque toutes les implémentations de std :: strings sont terminées par null, donc vous ne devriez probablement pas faire cela. Notez que "a \ 0b" comporte en fait quatre caractères en raison du terminateur nul automatique (a, null, b, null). Si vous voulez vraiment faire cela et rompre le contrat de std :: string, vous pouvez faire:

std::string s("aab");
s.at(1) = '\0';

mais si vous le faites, tous vos amis se moqueront de vous, vous ne trouverez jamais le vrai bonheur.

Jurney
la source
1
std :: string n'est PAS nécessaire pour être terminé par NULL.
Martin York
2
Ce n'est pas obligatoire, mais dans presque toutes les implémentations, c'est probablement à cause de la nécessité pour l'accesseur c_str () de vous fournir l'équivalent terminé par null.
Jurney
2
Pour plus d'efficacité, un caractère nul peut être conservé à l'arrière du tampon de données. Mais aucune des opérations (c'est-à-dire des méthodes) sur une chaîne n'utilise cette connaissance ou n'est affectée par une chaîne contenant un caractère NULL. Le caractère NULL sera manipulé exactement de la même manière que tout autre caractère.
Martin York
C'est pourquoi il est si drôle que string soit std :: - son comportement n'est défini sur AUCUNE plateforme.
Je souhaite que user595447 soit toujours là pour que je puisse leur demander de quoi diable ils pensaient parler.
underscore_d