2017-09-08 3 views
0

Si j'ai un terminateur null intégré [de côté: est-ce UB?], Est-il bien défini pour moi d'accéder aux valeurs après lui?L'accès à des parties d'une chaîne après un bloc de terminaison null incorporé UB?

#include <stdio.h> 

const char foo[] = "abc\0def"; 
int main() { 
    printf("%s", foo+4); 
    return sizeof(foo); 
} 

Pour mémoire, il imprime ce que vous pourriez attendre:

def 
+3

Non, la mémoire est allouée et il n'y a pas d'UB dans ce cas. – user0042

+1

C'est bien sauf que 'sizeof' retournera la taille du tableau, pas la longueur de la chaîne (si c'est ce que vous vouliez). –

Répondre

3

Un embarqué null est pas non défini Comportement. Il pourrait être une erreur de logique, si vous travaillez avec des fonctions qui s'attendent à ce que les chaînes à terminaison nulle. Mais il n'y a rien de mal, de mal ou d'indéfini dans l'accès à la totalité d'un tableau que vous avez réussi à allouer, quel que soit son contenu.

Une chose à observer si: si vous essayez de stocker ces données dans un std::string (ce qui est la façon dont vous devez gérer toutes les chaînes, TBH), comment vous stocker la chaîne peut être importante.

std::string str1 = foo; //contents of str1 becomes "abc". 
std::string str2 = std::string(foo, sizeof(foo)); //contents of str2 becomes "abc\0def" 
2

[dcl.init.string] Etats

Un tableau de type de caractère étroit (3.9.1), un tableau char16_t, tableau char32_t ou le tableau wchar_t peut être initialisé par une chaîne littérale étroite , littéral de chaîne char16_t, littéral de chaîne char32_t ou littéral de chaîne large, respectivement, ou par un littéral de chaîne correctement typé entouré d'accolades (2.14.5). Les caractères successifs de la valeur de la chaîne littérale initialisent les éléments du tableau.

mine accent

Ainsi, le nul-il pas un problème intégré, il devient juste un élément du tableau. Puisque le tableau est dimensionné pour contenir tous les caractères et les séquences d'échappement, nous savons qu'il y a des éléments après cette valeur nulle et qu'il est sûr d'y accéder. Vraiment le seul problème avec le null incorporé est que n'importe quelle fonction de C s'arrêtera quand elle frappe cette null et ne traitera pas complètement la chaîne. Vous pourriez envisager d'utiliser un std::string à la place qui n'a pas ces problèmes.

2

L'accès à une chaîne C au-delà du caractère nul final en soi n'est jamais un comportement indéfini. Pourtant, nous pouvons rendement de comportement non défini de cette façon, mais pour une toute autre raison:

Si le caractère nul final arrive à résider à la dernière position dans le tableau de caractères réservé à la chaîne, nous accéder à ce tableau sous-jacent hors de ses limites si nous accédons à la chaîne au-delà de sa fin. Ce hors-limites d'accès est ce qui donne vraiment le comportement non défini ...

Edit:

[de côté: que UB]

UB, comportement non défini, est un comportement qui ne peut pas être défini, car il n'y a pas de comportement significatif pour. S'appuyer sur un comportement indéfini peut entraîner n'importe quoi, y compris obtenir les résultats attendus, mais peut échouer lamentablement à tout autre moment (par exemple.sur une autre plate-forme, après avoir changé de version du compilateur, après simplement recompiler, même après avoir redémarré un seul et même programme). Ainsi, un programme reposant sur un comportement indéfini est considéré comme n'étant pas bien défini. Exemple: Déréférencement d'un pointeur vers un objet qui a déjà été supprimé (un "pointeur flottant"), ou proche de la question: accès à un tableau hors limites (peut entraîner l'accès à la mémoire non processus en cours ou même pas existant, mais pourrait lire ou (mauvais !!!) écraser la mémoire d'un objet totalement différent qui se trouve à l'adresse donnée (il ne doit même pas être le même objet à chaque fois que votre programme s'exécute, pas même dans un seul programme)

Un comportement indéfini ne doit pas être confondu avec un comportement non spécifié (ou de manière synonyme, comportement défini par l'implémentation): dans ce cas, le comportement pour une entrée donnée est bien défini, mais il est laissé au fournisseur du compilateur pour définir le comportement dans certaines limites raisonnables données. Exemple: décalage vers la droite des entiers négatifs - il peut se produire avec ou sans extension de signe (ce qui peut être un décalage arithmétique ou logique). Lequel s'applique n'est pas spécifié par la norme, mais l'utilisation du décalage droit sur les entiers négatifs est bien définie.

+0

Sur le côté: J'ai demandé parce qu'il semble qu'une optimisation raisonnable pourrait être de tout laisser tomber après le premier terminateur null - comme il pourrait être spécifié, mais je ne savais pas si c'est le cas. "J'ai essayé de compiler sur godbolt et j'ai noté ce qui se passe réellement." – wrhall

+0

"drop after the first null terminator" - pas 100% ce que vous voulez dire par là - le tableau est là, il a une taille fixe, et vous ne pouvez pas une partie de celui-ci. Si vous voulez dire "réutiliser dans un but différent" - vous êtes absolument libre de le faire - assurez-vous que vous ne dépassez pas les limites du tableau ... – Aconcagua

+0

Désolé - il semble que l'optimisation d'un compilateur * raisonnable puisse être abandonner tout après un terminateur nul ... c'est-à-dire ne pas allouer un tableau au-delà du terminateur nul. – wrhall