2016-02-23 23 views
6

Durante la scrittura di un codice C, mi sono imbattuto in un piccolo problema in cui dovevo convertire un carattere in una "stringa" (un po 'di memoria il cui inizio è dato da un puntatore char*).Questo comportamento del cast (char *) & x è ben definito?

L'idea è che se qualche sourcestr puntatore viene (non NULL), quindi dovrei usarlo come il mio "stringa finale", altrimenti dovrei convertire un dato charcode nel primo personaggio di un altro array, e utilizzarlo al posto .

Ai fini di questa domanda, assumeremo che i tipi di variabili non possano essere modificati in anticipo. In altre parole, non posso semplicemente memorizzare il mio charcode come const char* anziché int.

Poiché tendo ad essere pigro, ho pensato tra me e me: "hey, non potrei semplicemente usare l'indirizzo del personaggio e trattare quel puntatore come una stringa?". Ecco un piccolo frammento di quello che ho scritto (non fracassare la testa contro il muro appena ancora!):

int charcode = FOO; /* Assume this is always valid ASCII. */ 

char* sourcestr = "BAR"; /* Case #1 */ 
char* sourcestr = NULL; /* Case #2 */ 

char* finalstr = sourcestr ? sourcestr : (char*)&charcode; 

Ora, naturalmente, ho provato, e come mi aspettavo, funziona. Anche con alcuni flag di avviso, il compilatore è ancora felice. Tuttavia, ho questa strana sensazione che questo è in realtà un comportamento indefinito, e che non dovrei farlo.

Il motivo per cui penso in questo modo è perché gli array char* devono essere terminati con null per essere stampati correttamente come stringhe (e io voglio che sia il mio!). Tuttavia, non ho la certezza che il valore a &charcode + 1 sarà zero, quindi potrei finire con qualche follia di overflow del buffer.

C'è un motivo reale per cui funziona correttamente oppure sono stato solo fortunato a ottenere gli zeri nei punti giusti quando ho provato?

(Si noti che non sto cercando altri modi per ottenere la conversione. Potrei semplicemente utilizzare una variabile char tmp[2] = {0}, e mettere il mio carattere in corrispondenza dell'indice 0. Potrei anche usare qualcosa come sprintf o snprintf, a condizione che 'm abbastanza attenti con buffer overflow C'è una miriade di modi per farlo, io sono solo interessato al comportamento di questa particolare operazione getto)

Edit:.. ho visto alcune persone chiamano questo Hackery, e siamo chiari: sono completamente d'accordo con te. Non sono abbastanza un masochista per farlo nel codice rilasciato. Questo è solo io che mi incuriosisce;)

+0

Nel caso 2, se si stampa quel puntatore come stringa, allora no, non è definito. – 2501

+0

'char *' è un puntatore. C non ha un tipo di stringa. L'avviso del compilatore esiste per buone ragioni. Prestare attenzione a loro. – Olaf

+0

In sé e per sé non è UB, tuttavia quando lo tratti come una stringa (come stampandola con printf o simili) diventa UB – Magisch

risposta

0

Questo è un comportamento assolutamente non definito per le seguenti ragioni:

  1. Meno probabile, ma da considerare quando strettamente riferimento alle norme: non si può assumere l'int sizeof sulla macchina/sistema in cui codice sarà compilato
  2. Come sopra non è possibile assumere il codeset. Per esempio. cosa succede su una macchina/sistema EBCDIC?
  3. Facile dire che la vostra macchina ha un processore little endian. Su macchine big endian il codice non funziona a a causa del layout di memoria big-endian.
  4. Perché su molti sistemi char è un intero con segno, come è int, quando il char è un valore negativo (cioè char>127 su macchine con 8bits char), potrebbe fallire a causa di firmare un'estensione se si assegna il valore come in il codice di seguito

codice:

char ch = FOO; 
int charcode = ch; 

PS Riguardo al punto 3: la stringa sarà effettivamente terminata NULL in una piccola macchina endian con sizeof(int)>sizeof(char) e char con un valore positivo, poiché l'MSB di int sarà 0 e il layout di memoria per tale endianità è LSB-MSB (LSB prima).

+0

Perché farebbe la differenza se la codifica fosse EBCDIC? Finché la fonte originale di FOO era un carattere letterale o un carattere letto come con fgetc, dovrebbe andare bene. – rici

+0

@rici solo perché alcuni simboli che in ascii sono un intero apositivo in EBCDIC possono essere negativi. Forse questo è un eccesso di cautela :-) –

5

Il tuo codice è ben definito in quanto puoi sempre trasmettere a char*. Ma alcuni problemi:

  1. Nota che "BAR" è un const char* letterale - in modo da non tentare di modificare il contenuto. Che sarebbe essere indefinito.

  2. Non tentare di utilizzare (char*)&charcode come parametro per nessuna delle funzioni stringa nella libreria standard C. Sarà non essere terminato con null. Quindi in questo senso, è possibile che non sia trattarlo come una stringa.

  3. aritmetica dei puntatori su (char*)&charcodesarà valida fino al un passato lo scalare charcode. Ma non tentativo di dereference qualsiasi puntatore oltre lo charcode stesso. L'intervallo di n per cui l'espressione (char*)&charcode + n è valida dipende da sizeof(int).

3

Il getto e l'assegnazione, char* finalstr = (char*)&charcode; sono definiti.

La stampa finalstr con printf come stringa, %s, se punta a charcode è un comportamento non definito.

Piuttosto che ricorrere a hackery e nascondere una stringa in un tipo int, convertire i valori memorizzati nel numero intero in una stringa utilizzando una funzione di conversione selezionata. Un possibile esempio è:

char str[32] = { 0 }; 
snprintf(str , 32 , "%d" , charcode); 
char* finalstr = sourcestr ? sourcestr : str; 

o utilizzare qualsiasi altra conversione (definita!) Che ti piace.

+0

La stampa in senso stretto è UB solo se il valore non contiene zero byte. Se ha zero byte, è l'implementazione definita se è UB. Ad esempio il valore 'charcode'' 'A'' sarebbe OK sul sistema little-endian con charset ASCII. – user694733

+0

@ user694733 Non lo sarebbe. L'identificatore% s deve ricevere una stringa, cioè una matrice di 'tipo di carattere', che non digita' int', altrimenti il ​​comportamento non è definito. Vedi 7.21.6.1. paragrafo 8.e 9 (Anche se int è costituito da byte, non è un array di caratteri.) – 2501

+0

@ 2501: bozza standard C11 '6.5 Espressioni, Sezione 7 Un oggetto deve avere il suo valore memorizzato accessibile solo da un'espressione lvalue che ha uno dei seguenti tipi: [...] - un tipo di carattere. Puoi trattare * tutto * come una matrice di 'char'. L'ordine dei byte dipenderà dall'implementazione, ma non è * non definito *, a patto che ci sia una terminazione nulla. – EOF

2

Come altri ha detto che funziona perché la rappresentazione interna di un int sulla tua macchina è little endian e il tuo char è più piccolo di un int. Anche il valore ASCII del tuo personaggio è sotto 128 o hai caratteri non firmati (altrimenti ci sarebbe l'estensione del segno). Ciò significa che il valore del carattere è nel/i byte/i inferiore/i della rappresentazione dell'int e il resto dell'int sarà tutti zeri (assumendo qualsiasi rappresentazione normale di un int). Non sei "fortunato", hai una macchina abbastanza normale.

È anche un comportamento completamente indefinito dare quel puntatore char a qualsiasi funzione che si aspetta una stringa.Potresti farla franca adesso ma il compilatore è libero di ottimizzarlo in qualcosa di completamente diverso.

Per esempio se si fa un printf subito dopo che l'assegnazione, il compilatore è libero di assumere che avrete sempre passa una stringa valida per printf il che significa che il controllo per sourcestr essere NULL non è necessaria, perché se sourcestr era NULL printf verrebbe chiamato con qualcosa che non è una stringa e il compilatore è libero di assumere che un comportamento indefinito non avvenga mai. Ciò significa che qualsiasi controllo di sourcestr NULL prima o dopo tale assegnazione non è necessario perché il compilatore sa già che non è NULL. Questa assunzione è autorizzata a diffondersi ovunque nel tuo codice.

Questa è stata raramente una cosa di cui preoccuparsi e si potrebbe cavarsela con trucchi più brutto di questo fino a un decennio fa o giù di lì, quando gli scrittori del compilatore hanno iniziato una corsa agli armamenti su quanto possono seguire lo standard C alla lettera di scappare con ottimizzazioni sempre più brutali. Oggi i compilatori stanno diventando sempre più aggressivi e mentre l'ottimizzazione che ho ipotizzato probabilmente non esiste ancora, se una persona del compilatore lo vede, probabilmente lo implementeranno solo perché possono farlo.

Problemi correlati