Comment distinguer les chaînes dans le tas ou les littéraux?

J’ai un cas d’utilisation où je peux obtenir des pointeurs de chaînes allouées en mémoire ou en littéraux. Maintenant, ce dernier ne peut pas être libéré, donc c’est un problème si je passe le mauvais. Y a-t-il un moyen de savoir lequel est atsortingbué et lequel ne l’est pas?

char *b = "dont free me!"; if(!IS_LITERAL(b)) { free(b); } 

J’imagine quelque chose comme ça.

Mon exemple:

Scénario 1: littéral

 char *b = "dont free me!"; scruct elem* my_element = mylib_create_element(b); // do smth int result = mylib_destroy_element(my_element); // free literal, very bad 

Scénario 2: en tas

 char *b = malloc(sizeof(char)*17); // example strncpy(b, "you can free me!",17); scruct elem* my_element = mylib_create_element(b); // do smth int result = mylib_destroy_element(my_element); // free heap, nice 

Comment l’utilisateur appelle mylib_create_element(b); n’est pas sous mon contrôle. S’il libère avant mylib_destroy_element il peut se bloquer. Il faut donc que ce soit mylib_destroy_element qui nettoie.

J’ai eu un cas similaire récemment. Voici ce que j’ai fait:

Si vous créez une API qui accepte un pointeur de chaîne et l’utilise ensuite pour créer un object ( mylib_create_element ), une bonne idée serait de copier la chaîne dans un tampon de segment de mémoire séparé, puis de le libérer à votre discrétion. De cette façon, l’utilisateur est responsable de la libération de la chaîne qu’il a utilisée lors de l’appel de votre API, ce qui est logique. C’est sa corde, après tout.

Notez que cela ne fonctionnera pas si votre API dépend de la modification de la chaîne par l’utilisateur après la création de l’object!

Sur la plupart des Unix, il existe des valeurs ‘etext’ et ‘edata’. Si votre pointeur se situe entre ‘etext’ et ‘edata’, il doit être initialisé de manière statique. Ces valeurs n’étant mentionnées dans aucune norme, leur utilisation est non portable et à vos risques et périls.

Exemple:

 #include #include extern char edata; extern char etext; #define IS_LITERAL(b) ((b) >= &etext && (b) < &edata) int main() { char *p1 = "static"; char *p2 = malloc(10); printf("%d, %d\n", IS_LITERAL(p1), IS_LITERAL(p2)); } 

Vous pouvez uniquement demander à l’utilisateur de marquer explicitement leur entrée comme chaîne littérale ou allouée.

Toutefois, comme @ Mints97 le mentionne dans sa réponse, cette approche est fondamentalement architecturalement incorrecte: vous forcez l’utilisateur de votre bibliothèque à effectuer certaines actions explicites; s’il l’oublie, cela risque fort de provoquer une fuite de mémoire (ou même un blocage de l’application). . Alors utilisez-le uniquement si :

  1. Vous voulez réduire considérablement le montant des allocations. Dans mon cas, il s’agissait de noms de nœuds JSON, qui ne changent jamais pendant la durée de vie d’un programme.
  2. Vous avez un bon contrôle du code des consommateurs de votre bibliothèque. Dans mon cas, les bibliothèques sont livrées avec les fichiers binarys et étroitement liées à ceux-ci.

Exemple d’implémentation

 #define AAS_DYNAMIC 'D' #define AAS_STATIC 'S' #define AAS_STATIC_PREFIX "S" #define AAS_CONST_STR(str) ((AAS_STATIC_PREFIX str) + 1) char* aas_allocate(size_t count) { char* buffer = malloc(count + 2); if(buffer == NULL) return NULL; *buffer = AAS_DYNAMIC; return buffer + 1; } void aas_free(char* aas) { if(aas != NULL) { if(*(aas - 1) == AAS_DYNAMIC) { free(aas - 1); } } } ... char* s1 = AAS_CONST_STR("test1"); char* s2 = aas_allocate(10); strcpy(s2, "test2"); aas_free(s1); aas_free(s2); 

Test des performances (note n ° 1)

J’ai comparé ma bibliothèque libtsjson avec le code suivant (800 000 itérations):

  node = json_new_node(NULL); json_add_integer(node, NODE_NAME("i"), 10); json_add_ssortingng(node, NODE_NAME("s1"), json_str_create("test1")); json_add_ssortingng(node, NODE_NAME("s2"), json_str_create("test2")); json_node_destroy(node); 

Mon processeur est Intel Core i7 860. Si NODE_NAME est juste une macro, le temps par itération était de 479ns Si NODE_NAME est une allocation de mémoire, le temps par itération était de 609ns

Astuce utilisateur ou compilateur (note n ° 2)

  • Ajoutez un indice à tous ces pointeurs, c’est-à-dire que l’parsingur de source statique Linux Sparse peut détecter de tels problèmes

     char __autossortingng* s1 = aas_copy("test"); /* OK */ char __autossortingng* s2 = strdup("test"); /* Should be fail? */ char* s3 = s1; /* Confuses sparse */ char* s4 = (char*) s1; /* Explicit conversion - OK */ 

(pas tout à fait sûr des sorties de Sparse)

  • Utilisez typedef simple pour que le compilateur déclenche un avertissement lorsque vous faites quelque chose de mal:

     #ifdef AAS_STRICT typedef struct { char a; } *aas_t; #else typedef char *aas_t; #endif 

Cette approche est un pas de plus vers un monde de hacks en C sales, c’est-à-dire que sizeof(*aas_t) est maintenant> 1.

La source complète avec les modifications peut être trouvée ici. Si compilé avec -DAAS_STRICT des erreurs seront -DAAS_STRICT : https://ideone.com/xxmSat Même si le code est correct, il peut se plaindre de strcpy() (non reproduit sur ideone).

La réponse simple est que vous ne pouvez pas faire cela car le langage C ne délimite pas la stack, le tas et la section de données.

Si vous voulez deviner, vous pouvez collecter l’adresse de la première variable de la stack, l’adresse de la fonction appelante et l’adresse d’un octet de mémoire alloué à heap; et comparez-le ensuite avec votre pointeur – une très mauvaise pratique sans aucune garantie.

Il est préférable que vous modifiiez votre code de manière à ne pas rencontrer ce problème.

Voici un moyen pratique:

Bien que la norme de langage C ne le dicte pas , pour toutes les occurrences identiques d’une chaîne littérale donnée dans votre code, le compilateur génère une copie unique dans la section de données RO de l’image exécutable.

En d’autres termes, chaque occurrence de la chaîne littérale "dont free me!" dans votre code est traduit dans la même adresse de la mémoire.

Donc, au moment où vous voulez désallouer cette chaîne, vous pouvez simplement comparer son adresse avec l’adresse de la chaîne littérale "dont free me!" :

 if (b != "dont free me!") // address comparison free(b); 

Pour souligner encore une fois, cela n’est pas imposé par le standard du langage C, mais par un compilateur décent du langage.


Ce qui précède n’est qu’une astuce pratique qui renvoie directement à la question à traiter (plutôt qu’à la motivation qui sous-tend cette question).

Ssortingctement parlant, si vous avez atteint un point dans votre implémentation dans lequel vous devez faire la distinction entre une chaîne allouée de manière statique et une chaîne allouée de manière dynamic, j’aurais tendance à deviner que votre conception initiale est défectueuse quelque part sur la ligne.

C’est précisément pour cette raison que seule la partie de code ou le module qui a créé une chaîne peut la libérer. En d’autres termes, chaque chaîne ou donnée est “détenue” par l’unité de code qui l’a créée. Seul le propriétaire peut le libérer. Une fonction ne doit jamais libérer les structures de données qu’elle a reçues en tant qu’arguments.

Vous pouvez faire ce qui suit:

  typedef struct { int is_literal; char * array; } elem; 

Chaque fois que vous allouez elem.array sur le tas, définissez simplement is_literal sur 0. Lorsque vous définissez le tableau comme étant littéral, définissez le drapeau sur une valeur non nulle, par exemple:

 elem foo; foo.array = "literal"; foo.is_literal = 1 ; 

ou

 elem bar; bar.array = (char*) (malloc(sizeof(char) * 10)) ; bar.is_literal = 0; 

Puis côté client:

 if(!bar.is_literal) { free(bar.array); } 

Aussi simple que cela.

À l’époque des débuts, alors qu’un 80386 pouvait avoir 8 mégaoctets de RAM maximum et que les idées de création d’objects étaient expliquées dans tous les autres articles de magazine, je n’aimais pas copier parfaitement de bons littéraux dans des objects chaîne (atsortingbuer et libérer la copie interne). ) et j’ai interrogé Bjarne à ce sujet, car une classe de chaîne brute était l’un de ses exemples de fancy-stuff C ++.

Il a dit ne t’inquiète pas pour ça.

Est-ce que cela a à voir avec les littéraux vs autres char* pointeurs? Vous pouvez toujours posséder la mémoire. Je pense donc à vos idées de recherche de différents segments de mémoire.

Ou est-ce plus généralement que la propriété peut être ou ne pas être atsortingbuée, il n’ya aucun moyen de le savoir et il est nécessaire de stocker un drapeau: “hé, c’est un object de tas, mais quelqu’un d’autre l’utilise encore et s’en occupera plus tard, ok?”

Pour le cas facile où il est “sur le tas” ou “pas” (littéraux, globals, basés sur la stack), vous pourriez avoir la fonction free savoir . Si vous avez fourni un ensemble correspondant d’ allocate / maybe-free , vous pouvez l’écrire pour savoir quelle mémoire est sous son contrôle.