Le funzioni distinte hanno indirizzi distinti?

Considera queste due funzioni:

void foo() {} void bar() {} 

è garantito che &foo != &bar ?

Allo stesso modo,

 template void foo() { } 

è garantito che &foo != &foo ?


Esistono due linker che conosco insieme alle definizioni delle funzioni di piega.

MSVC crea in modo aggressivo le funzioni di piegatura di COMDAT, quindi due funzioni con la stessa implementazione possono essere trasformate in un’unica funzione. Come effetto collaterale, le due funzioni condividono lo stesso indirizzo. Avevo l’impressione che fosse illegale, ma non riesco a trovare dove sia illegale nella norma.

Il linker Gold ripiega anche le funzioni, con una safe e all impostazioni. safe significa che se viene preso un indirizzo di funzione, non viene piegato, mentre all pieghe anche se l’indirizzo è occupato. Quindi la safe dell’oro si comporta come se le funzioni avessero indirizzi distinti.

Mentre il folding potrebbe essere inaspettato, e c’è un codice che si basa su funzioni distinte (identiche di implementazione) con indirizzi diversi (quindi può essere pericoloso piegare), è effettivamente illegale secondo lo standard C ++ corrente? (C ++ 14 a questo punto) (Naturalmente, se safe ripiegamento safe è legale)

5.10 Operatori di uguaglianza [expr.eq]

1 Il gruppo di operatori == (uguale a) e != (Non uguale a) da sinistra a destra. Gli operandi devono avere aritmetica, enumerazione, puntatore o puntatore al tipo di membro o digitare std::nullptr_t . Gli operatori == e != Producono entrambi true o false , ovvero un risultato di tipo bool . In ogni caso qui sotto, gli operandi devono avere lo stesso tipo dopo che sono state applicate le conversioni specificate .
2 Se almeno uno degli operandi è un puntatore, le conversioni puntatore (4.10) e le conversioni di qualificazione (4.4) vengono eseguite su entrambi gli operandi per portarli al loro tipo di puntatore composito (Clausola 5). Il confronto dei puntatori è definito come segue: due puntatori confrontati uguali se entrambi sono nulli, entrambi puntano alla stessa funzione, o entrambi rappresentano lo stesso indirizzo (3.9.2), altrimenti si confrontano ineguali.

Prendiamo l’ultimo bit per bit:

  1. Due puntatori nulli sono uguali.
    Buono per la tua sanità mentale.
  2. Due puntatori alla stessa funzione confrontano uguali.
    Qualsiasi altra cosa sarebbe estremamente sorprendente.
    Significa anche che solo una versione fuori linea di qualsiasi funzione inline può mai avere il proprio indirizzo preso, a meno che non si vogliano rendere complicati e costosi i confronti tra puntatori e funzioni.
  3. Entrambi rappresentano lo stesso indirizzo.
    Ora questo è tutto. Eliminare questo e ridurre if and only if a un semplice if lo lascerebbe all’interpretazione, ma questo è un chiaro mandato per rendere identiche due funzioni , a condizione che non cambi in altro modo il comportamento osservabile di un programma conforms.

Sembra che il rapporto sui difetti 1400: l’uguaglianza dei puntatori a funzioni si occupi di questo problema e mi sembra che sia corretto fare questa ottimizzazione ma, come indicano i commenti, c’è disaccordo. Dice ( sottolineatura mia ):

Secondo il paragrafo 2 [expr.eq] 5.10, due puntatori di funzione si confrontano solo se puntano alla stessa funzione . Tuttavia, come ottimizzazione, le implementazioni sono alias funzioni che hanno definizioni identiche. Non è chiaro se lo Standard debba affrontare esplicitamente questa ottimizzazione o meno.

e la risposta è stata:

Lo standard è chiaro sui requisiti e le implementazioni sono libere di ottimizzare entro i limiti della regola “as-if” .

La domanda è rivolta a due problemi:

  • Va bene che questi suggerimenti siano considerati uguali
  • Va bene per unire le funzioni

In base ai commenti, vedo due interpretazioni della risposta:

  1. Questa ottimizzazione è ok, lo standard dà l’implementazione di questa libertà secondo la regola as-if . La regola as-if è trattata nella sezione 1.9 e significa che l’implementazione deve solo emulare il comportamento osservabile rispetto ai requisiti dello standard. Questa è ancora la mia interpretazione della risposta.

  2. Il problema è a portata di mano è completamente ignorato e la dichiarazione dice semplicemente che non è richiesto alcun adeguamento allo standard perché chiaramente le regole as-if lo coprono, ma l’interpretazione è lasciata come esercizio al lettore. Sebbene riconosca a causa della scarsa chiarezza della risposta, non posso ignorare questa opinione, ma si tratta di una risposta assolutamente inutile. Sembra anche incoerente con le risposte nelle altre questioni NAD che, per quanto posso dire, evidenziano il problema se esistono.

Cosa dice la bozza dello standard

Poiché sappiamo che abbiamo a che fare con la regola as-if , possiamo iniziare da lì e notare che la sezione 1.8 dice:

A meno che un object non sia un campo di bit o un sottoobject di class base di dimensione zero, l’indirizzo di quell’object è l’indirizzo del primo byte che occupa. Due oggetti che non sono campi di bit possono avere lo stesso indirizzo se uno è un sub-object dell’altro, o se almeno uno è un sottoobject di class base di dimensione zero e sono di tipi diversi; altrimenti, avranno indirizzi distinti. 4

e la nota 4 dice:

Sotto la regola “as-if” un’implementazione è autorizzata a memorizzare due oggetti sullo stesso indirizzo macchina o non memorizzare affatto un object se il programma non può osservare la differenza

ma una nota da quella sezione dice:

Una funzione non è un object, indipendentemente dal fatto che occupi o meno l’archiviazione nel modo in cui gli oggetti lo fanno

sebbene non sia normativo, i requisiti per un object di cui al paragrafo 1 non hanno senso nel contesto di una funzione e quindi sono coerenti con questa nota. Quindi siamo esplicitamente limitati dagli oggetti di aliasing con alcune eccezioni, ma non tali restrizioni si applicano alle funzioni.

Poi abbiamo la sezione 5.10 Operatori di uguaglianza che dice ( sottolineatura mia ):

[…] Due puntatori si confrontano uguali se sono entrambi nulli, entrambi puntano alla stessa funzione, o entrambi rappresentano lo stesso indirizzo (3.9.2), altrimenti si confrontano ineguali.

che ci dice che due puntatori sono uguali se sono:

  • Puntatori nulli
  • Punta alla stessa funzione
  • Rappresenta lo stesso indirizzo

Il o entrambi rappresentano lo stesso indirizzo sembra dare abbastanza latitudine per consentire a un compilatore di alias due diverse funzioni e non richiede puntatori a diverse funzioni per confrontare ineguale.

osservazioni

Keith Thompson ha fatto alcune grandi osservazioni che credo valga la pena di aggiungere alla risposta dal momento che arrivano alle questioni fondamentali coinvolte, dice:

Se un programma stampa il risultato di & foo == & bar , questo è un comportamento osservabile; l’ottimizzazione in questione modifica il comportamento osservabile.

che sono d’accordo e se potessimo dimostrare che vi è un obbligo per i puntatori di essere ineguali che violerebbe effettivamente la regola as-if ma fino ad ora non possiamo dimostrarlo.

e:

[…] considera un programma che definisce la funzione vuota e usa i loro indirizzi come valori univoci (pensa a SIG_DFL , SIG_ERR e SIG_IGN in / ). Assegnandoli allo stesso indirizzo si romperebbe un programma del genere

Come ho notato nel mio commento, lo standard C richiede che queste macro generino valori distinti , da 7.14 in C11:

[…] che si espandono in espressioni costanti con valori distinti che hanno il tipo compatibile con il secondo argomento e il valore restituito dalla funzione segnale e i cui valori si confrontano in modo disuguale rispetto all’indirizzo di qualsiasi funzione dichiarabile […]

Quindi, anche se questo caso è coperto, forse ci sono altri casi che renderebbero pericolosa questa ottimizzazione.

Aggiornare

Jan Hubička, uno sviluppatore di gcc scritto un post sul blog Miglioramento dei tempi di collegamento e di ottimizzazione inter-procedurale in GCC 5 , la piegatura del codice era uno dei molti argomenti trattati.

Gli ho chiesto di commentare se piegare funzioni identiche allo stesso indirizzo fosse un comportamento conforms o meno e lui dice che non è un comportamento conforms e infatti una simile ottimizzazione interromperà gcc stesso:

Non è conforms a girare due funzioni per avere lo stesso indirizzo, quindi MSVC è piuttosto aggressivo qui. Facendolo, per esempio, si interrompe GCC stesso perché per il mio indirizzo a sorpresa il confronto è fatto nel codice delle intestazioni precompilate. Funziona per molti altri progetti, incluso Firefox.

Col senno di poi, dopo mesi passati a leggere i rapporti sui difetti ea pensare ai problemi di ottimizzazione, sono incline a una lettura più prudente della risposta del comitato. Prendere l’indirizzo di una funzione è un comportamento osservabile e quindi piegare funzioni identiche violerebbe la regola as-if .

Sì. Dallo standard (§5.10 / 1): “Due puntatori dello stesso tipo sono uguali se e solo se entrambi sono nulli, entrambi puntano alla stessa funzione, o entrambi rappresentano lo stesso indirizzo”

Una volta che sono stati istanziati, foo e foo sono due funzioni diverse, quindi vale anche per loro.

Quindi la parte problematica è chiaramente la frase o entrambi rappresentano lo stesso indirizzo (3.9.2) .

IMO questa parte è chiaramente lì per definire la semantica per i tipi di puntatori object. E solo per i tipi di puntatore a oggetti.

La frase fa riferimento alla sezione 3.9.2, il che significa che dovremmo guardare lì. 3.9.2 parla (tra gli altri) degli indirizzi rappresentati dai puntatori di oggetti. Non parla degli indirizzi che rappresentano i puntatori di funzione. Quale, IMO, lascia solo due possibili interpretazioni:

1) La frase semplicemente non si applica ai puntatori di funzione. Che lascia solo i due puntatori nulli e due puntatori alla stessa funzione confrontando lo stesso, che è quello che probabilmente la maggior parte di noi si aspetta.

2) La frase si applica. Dal momento che si riferisce al 3.9.2, che non dice nulla sugli indirizzi che i puntatori di funzione rappresentano, possiamo fare in modo che due puntatori di funzioni siano uguali. Il che è molto inaspettato e ovviamente rende completamente inutile il confronto tra i puntatori di funzione.

Quindi, mentre tecnicamente si potrebbe sostenere che (2) è un’interpretazione valida , IMO non è un’interpretazione significativa e quindi dovrebbe essere ignorata. E poiché non tutti sembrano essere d’accordo su questo, penso anche che sia necessario un chiarimento nello standard.