Un puntatore (indirizzo) può mai essere negativo?

Ho una funzione che mi piacerebbe poter restituire valori speciali per i fallimenti e non inizializzati (restituisce un puntatore al successo).

Attualmente restituisce NULL per errore e -1 per non inizializzato, e questo sembra funzionare … ma potrei essere un imbroglio del sistema. iirc, gli indirizzi sono sempre positivi, no? (anche se il compilatore mi permette di impostare un indirizzo su -1, questo sembra strano).

[aggiornamento] Un’altra idea che ho avuto (nel caso in cui -1 fosse rischioso) è quella di malloc a char @ l’ambito globale, e usare quell’indirizzo come sentinella.

No, gli indirizzi non sono sempre positivi – su x86_64, i puntatori sono estesi con il segno e lo spazio degli indirizzi è raggruppato simmetricamente attorno a 0 (sebbene sia normale che gli indirizzi “negativi” siano indirizzi del kernel).

Tuttavia, il punto è principalmente moot, poiché C definisce solo il significato di < e > confronti tra puntatori che fanno parte dello stesso object, o uno dopo la fine di un array. Puntatori a oggetti completamente diversi non possono essere confrontati in modo significativo se non per l'uguaglianza esatta, almeno nello standard C - if (p < NULL) non ha una semantica ben definita.

Dovresti creare un object fittizio con durata di archiviazione statica e utilizzare il suo indirizzo come valore non personalizzato:

 extern char uninit_sentinel; #define UNINITIALISED ((void *)&uninit_sentinel) 

È garantito avere un unico, unico indirizzo nel tuo programma.

I valori validi per un puntatore dipendono interamente dall’implementazione, quindi, sì, l’indirizzo di un puntatore potrebbe essere negativo.

Ancora più importante, tuttavia, considerare (come esempio di una ansible scelta di implementazione) il caso in cui ci si trova su una piattaforma a 32 bit con una dimensione del puntatore a 32 bit. Qualsiasi valore che può essere rappresentato da quel valore a 32 bit potrebbe essere un puntatore valido. Oltre al puntatore nullo, qualsiasi valore del puntatore potrebbe essere un puntatore valido per un object.

Per il tuo caso d’uso specifico, dovresti considerare di restituire un codice di stato e magari prendere il puntatore come parametro per la funzione.

In genere è un cattivo progetto cercare di multiplexare valori speciali su un valore di ritorno … stai provando a fare troppo con un singolo valore. Sarebbe più pulito restituire il “puntatore di successo” tramite argomento, piuttosto che il valore di ritorno. Questo lascia molto spazio non conflittuale nel valore di ritorno per tutte le condizioni che vuoi descrivere:

 int SomeFunction(SomeType **p) { *p = NULL; if (/* check for uninitialized ... */) return UNINITIALIZED; if (/* check for failure ... */) return FAILURE; *p = yourValue; return SUCCESS; } 

Si dovrebbe anche fare un tipico controllo degli argomenti (assicurarsi che ‘p’ non sia NULL).

Il linguaggio C non definisce la nozione di “negatività” per i puntatori. La proprietà di “essere negativo” è principalmente aritmetica, non in alcun modo applicabile ai valori del tipo di puntatore.

Se si dispone di una funzione di ritorno puntatore, non è ansible restituire in modo significativo il valore di -1 da tale funzione. Nel linguaggio C i valori integrali (diversi da zero) non sono convertibili implicitamente in tipi di puntatore. Un tentativo di restituire -1 da una funzione di ritorno puntatore è una violazione immediata del vincolo che determinerà un messaggio diagnostico. In breve, è un errore. Se il compilatore lo consente, significa semplicemente che non impone questo vincolo troppo rigorosamente (il più delle volte lo fanno per compatibilità con il codice pre-standard).

Se si impone il valore di -1 al tipo di puntatore da un cast esplicito, il risultato del cast sarà definito dall’implementazione. Il linguaggio stesso non fornisce garanzie in merito. Potrebbe facilmente dimostrarsi uguale a qualche altro valore di puntatore valido.

Se si desidera creare un valore di puntatore riservato, non è necessario alcun malloc . Puoi semplicemente dichiarare una variabile globale del tipo desiderato e usare il suo indirizzo come valore riservato. È garantito che sia unico.

I puntatori possono essere negativi come un intero senza segno può essere negativo. Cioè, certo, nell’interpretazione di un complemento a due, potresti interpretare il valore numerico come negativo perché il bit più significativo è attivo.

Qual è la differenza tra fallimento e unità. Se l’unità non è un altro tipo di errore, probabilmente si desidera riprogettare l’interfaccia per separare queste due condizioni.

Probabilmente il modo migliore per farlo è restituire il risultato attraverso un parametro, quindi il valore restituito indica solo un errore. Ad esempio, dove scrivere:

 void* func(); void* result=func(); if (result==0) /* handle error */ else if (result==-1) /* unitialized */ else /* initialized */ 

Cambia questo a

 // sets the *a to the returned object // *a will be null if the object has not been initialized // returns true on success, false otherwise int func(void** a); void* result; if (func(&result)){ /* handle error */ return; } /*do real stuff now*/ if (!result){ /* initialize */ } /* continue using the result now that it's been initialized */ 

@James è corretto, ovviamente, ma vorrei aggiungere che i puntatori non rappresentano sempre indirizzi di memoria assoluti , che in teoria sarebbero sempre positivi. I puntatori rappresentano anche gli indirizzi relativi a un certo punto della memoria, spesso un puntatore a pila o frame e quelli possono essere sia positivi che negativi.

Quindi la soluzione migliore è che la funzione accetti un puntatore a un puntatore come parametro e riempia quel puntatore con un valore di puntatore valido in caso di successo mentre restituisce un codice risultato dalla funzione effettiva.

La risposta di James è probabilmente corretta, ma ovviamente descrive una scelta di implementazione , non una scelta che puoi fare.

Personalmente, penso che gli indirizzi siano “intuitivamente” non firmati. Trovare un puntatore che paragona meno di un puntatore nullo sembrerebbe sbagliato. Ma ~0 e -1 , per lo stesso tipo intero, danno lo stesso valore. Se è intuitivamente senza segno, ~0 può rendere più intuitivo il valore del caso speciale – Lo uso per gli interi senza segno di errore molto. Non è molto diverso (zero è un int di default, quindi ~0 è -1 finché non lo lanci) ma sembra diverso.

Puntatori su sistemi a 32 bit possono utilizzare tutti i 32 bit in BTW, sebbene -1 o ~0 sia un puntatore estremamente improbabile che si verifichi per una vera allocazione nella pratica. Esistono anche regole specifiche per la piattaforma, ad esempio su Windows a 32 bit, un processo può avere solo uno spazio di indirizzamento di 2 GB, e c’è un sacco di codice attorno che codifica un qualche tipo di flag nel bit più in alto di un puntatore (ad esempio per il bilanciamento bandiere in alberi binari bilanciati).

NULL è l’unico ritorno di errore valido in questo caso, questo è vero ogni volta che viene restituito un valore senza segno come un puntatore. Può essere vero che in alcuni casi i punti non saranno abbastanza grandi da usare il bit del segno come un bit di dati, tuttavia poiché i puntatori sono controllati dal sistema operativo e non dal programma, non farei affidamento su questo comportamento.

Ricorda che un puntatore è fondamentalmente un valore a 32 bit; se questo sia o meno un numero negativo o sempre positivo è solo una questione di interpretazione (cioè) se il 32 ° bit è interpretato come il bit del segno o come un bit di dati. Quindi se interpretassi 0xFFFFFFF come un numero firmato sarebbe -1, se lo interpretassi come un numero senza segno sarebbe 4294967295. Tecnicamente, è improbabile che un puntatore sarebbe mai così grande, ma questo caso dovrebbe essere considerato comunque.

Per quanto riguarda un’alternativa, è ansible utilizzare un parametro out aggiuntivo (restituendo NULL per tutti gli errori), tuttavia ciò richiederebbe ai client di creare e passare un valore anche se non hanno bisogno di distinguere tra errori specifici di bettween.

Un’altra alternativa sarebbe utilizzare il meccanismo GetLastError / SetLastError per fornire ulteriori informazioni sugli errori (Questo sarebbe specifico per Windows, non so se si tratta di un problema o meno) o per generare un’eccezione in caso di errore.

Devin Ellingson

In realtà, (almeno su x86), l’eccezione puntatore NULL viene generata non solo dereferenziando il puntatore NULL, ma da un più ampio intervallo di indirizzi (ad esempio, primo 65kb). Questo aiuta a cogliere errori come

 int* x = NULL; x[10] = 1; 

Quindi, ci sono più indirizzi che sono garantiti per generare l’eccezione del puntatore NULL quando è dereferenziato. Ora considera questo codice (reso compilabile per AndreyT):

 #include  #include  #include  #define ERR_NOT_ENOUGH_MEM (int)NULL #define ERR_NEGATIVE (int)NULL + 1 #define ERR_NOT_DIGIT (int)NULL + 2 char* fn(int i){ if (i < 0) return (char*)ERR_NEGATIVE; if (i >= 10) return (char*)ERR_NOT_DIGIT; char* rez = (char*)malloc(strlen("Hello World ")+sizeof(char)*2); if (rez) sprintf(rez, "Hello World %d", i); return rez; }; int main(){ char* rez = fn(3); switch((int)rez){ case ERR_NOT_ENOUGH_MEM: printf("Not enough memory!\n"); break; case ERR_NEGATIVE: printf("The parameter was negative\n"); break; case ERR_NOT_DIGIT: printf("The parameter is not a digit\n"); break; default: printf("we received %s\n", rez); }; return 0; }; 

questo potrebbe essere utile in alcuni casi. Non funzionerà su alcune architetture di Harvard, ma funzionerà su quelle di von Neumann.

Non usare malloc per questo scopo. Potrebbe tenere legata la memoria non necessaria (se molta memoria è già in uso quando viene chiamato malloc e la sentinella viene allocata ad un indirizzo alto, ad esempio) e confonde i debugger / rilevatori di perdite di memoria. Invece, è sufficiente restituire un puntatore a un object static const char locale. Questo puntatore non comparirà mai uguale a qualsiasi puntatore che il programma potrebbe ottenere in qualsiasi altro modo, e spreca solo un byte di bss.

Positivo o negativo non è un aspetto significativo del tipo di puntatore. Riguardano il numero intero con segno incluso il char, short, int, ecc.

Le persone che parlano del puntatore negativo si trovano principalmente in una situazione che considera la rappresentazione della macchina del puntatore come un tipo intero. eg reinterpret_cast(ptr) . In questo caso, in realtà stanno parlando del numero intero casted. non il puntatore stesso.

In alcuni scenari, penso che il puntatore sia intrinsecamente non firmato, parliamo dell’indirizzo in termini inferiori o superiori. 0xFFFF.FFFF è sopra 0x0AAAA.0000 , che è intuitivo per gli esseri umani. sebbene 0xFFFF.FFFF sia in realtà un “negativo” mentre 0x0AAA.0000 è positivo.

Ma in altri scenari, la sottrazione del puntatore, (ptr1 - ptr2) , (ptr1 - ptr2) un valore firmato il cui tipo è ptrdiff_t, è incoerente quando si confronta con la sottrazione di intero, signed_int_a - signed_int_b risulta un tipo int con unsigned_int_a - unsigned_int_b , unsigned_int_a - unsigned_int_b produce un tipo unsigned. Ma per la sottrazione del puntatore, produce un tipo firmato, perché la semantica è la distanza tra due puntatori, l’unità è il numero di elementi.

In sintesi, suggerisco di trattare il tipo di puntatore come tipo standalone, ogni tipo ha il suo set di operazioni su di esso. Per i puntatori (puntatore funzione di esclusione, puntatore funzione membro e void *):

  1. Elemento dell’elenco
  2. + , + =

    ptr + any_integer_type

  3. - , – =

    ptr – any_integer_type

    ptr1 – ptr2

  4. ++ sia prefisso e suffisso

  5. – Sia prefisso e suffisso

Nota: non esiste alcuna operazione / * % per il puntatore. Suppone anche che il puntatore debba essere trattato come un tipo autonomo, invece di “Un tipo simile a int” o “Un tipo il cui tipo sottostante è int, quindi dovrebbe apparire come int”.