Vraag C / C ++: optimalisatie van wijzers naar stringconstanten


Bekijk deze code:

#include <iostream>
using namespace std;

int main()
{
    const char* str0 = "Watchmen";
    const char* str1 = "Watchmen";
    char* str2 = "Watchmen";
    char* str3 = "Watchmen";

    cerr << static_cast<void*>( const_cast<char*>( str0 ) ) << endl;
    cerr << static_cast<void*>( const_cast<char*>( str1 ) ) << endl;
    cerr << static_cast<void*>( str2 ) << endl;
    cerr << static_cast<void*>( str3 ) << endl;

    return 0;
}

Die een uitvoer als deze produceert:

0x443000
0x443000
0x443000
0x443000

Dit was op de g ++ compiler draait onder Cygwin. De aanwijzers wijzen allemaal naar dezelfde locatie, zelfs als er geen optimalisatie is ingeschakeld (-O0).

Werkt de compiler altijd zo veel dat alle tekenreeksconstanten worden doorzocht om te zien of ze gelijk zijn? Kan dit gedrag worden vertrouwd?


13
2018-03-27 15:17


oorsprong


antwoorden:


Het is een uiterst eenvoudige optimalisatie, waarschijnlijk zelfs zo dat de meeste compiler-schrijvers het zelfs helemaal niet als een optimalisatie beschouwen. Het instellen van de optimalisatievlag op het laagste niveau betekent echter niet 'wees volkomen naïef'.

Compilers kunnen variëren in hoe agressief ze zijn in het samenvoegen van dubbele letterlijke tekenreeksen. Ze zouden zich kunnen beperken tot een enkele subroutine - leg die vier verklaringen in verschillende functies in plaats van een enkele functie, en je zou andere resultaten kunnen zien. Anderen kunnen een volledige compilatie-eenheid doen. Anderen kunnen vertrouwen op de linker om verder samen te voegen tussen meerdere compilatie-eenheden.

U kunt niet op dit gedrag vertrouwen, tenzij de documentatie van uw specifieke compiler zegt dat dit wel kan. De taal zelf stelt in dit opzicht geen eisen. Ik zou op mijn hoede zijn om erop te vertrouwen in mijn eigen code, zelfs als portabiliteit geen probleem was, omdat gedrag kan veranderen, zelfs tussen verschillende versies van de compiler van een enkele leverancier.


15
2018-03-27 15:36



Er kan niet op worden vertrouwd, het is een optimalisatie die geen deel uitmaakt van een standaard.

Ik heb de overeenkomstige regels van uw code gewijzigd in:

const char* str0 = "Watchmen";
const char* str1 = "atchmen";
char* str2 = "tchmen";
char* str3 = "chmen";

De uitvoer voor het -O0 optimalisatieniveau is:

0x8048830
0x8048839
0x8048841
0x8048848

Maar voor de -O1 is het:

0x80487c0
0x80487c1
0x80487c2
0x80487c3

Zoals je kunt zien, hergebruikte GCC (v4.1.2) de eerste string in alle volgende substrings. Het is een compilerkeuze om stringconstanten in het geheugen te rangschikken.


29
2018-03-27 15:47



Jij zeker zou niet vertrouw op dat gedrag, maar de meeste compilers zullen dit doen. Elke letterlijke waarde ("Hallo", 42, enz.) Zal één keer worden opgeslagen, en eventuele verwijzingen ernaar zullen vanzelfsprekend tot die ene referentie worden opgelost.

Als u merkt dat u daarop moet vertrouwen, wees dan veilig en hercodeer als volgt:

char *watchmen = "Watchmen";
char *foo = watchmen;
char *bar = watchmen;

11
2018-03-27 15:28



Daar moet je uiteraard niet op rekenen. Een optimizer kan iets moeilijks voor je doen, en het zou moeten worden toegestaan ​​om dit te doen.

Het is echter heel gemeenschappelijk. Ik herinner me dat in '87 een klasgenoot de DEC C-compiler gebruikte en deze rare bug had waarin al zijn letterlijke 3's in 11's veranderden (nummers zijn misschien veranderd om de onschuldigen te beschermen). Hij deed zelfs een printf ("%d\n", 3) en het is afgedrukt 11.

Hij belde me omdat het zo raar was (waarom doet dat mensen denken aan mij?) En na ongeveer 30 minuten krabben vonden we de oorzaak. Het was ongeveer een lijn zoals deze:

if (3 = x) break;

Let op het enkele "=" teken. Ja, dat was een typfout. De compiler had een klein foutje en stond dit toe. Het effect was om al zijn letterlijke 3's in het hele programma om te zetten in wat er op dat moment in x gebeurde.

Hoe dan ook, het was duidelijk dat de C-compiler alle letterlijke 3's op dezelfde plaats plaatste. Als een C-compiler in de jaren 80 in staat was om dit te doen, kan het niet moeilijk zijn om te doen. Ik zou verwachten dat het heel gewoon is.


8
2018-03-27 16:04



Ik zou niet op het gedrag vertrouwen, omdat ik betwijfel of de C- of C ++ -standaard dit gedrag expliciet zou maken, maar het is logisch dat de compiler dit doet. Het is ook logisch dat het dit gedrag vertoont, zelfs als er geen optimalisatie is opgegeven voor de compiler; er zit geen compromis in.

Alle tekenreeksliteralen in C of C ++ (bijvoorbeeld "string literal") zijn alleen-lezen en dus constant. Wanneer je zegt:

char *s = "literal";

Je bent in zekere zin downcasting de string naar een niet-const type. Toch kun je het alleen-lezen attribuut van de string niet verwijderen: als je het probeert te manipuleren, zul je betrapt worden tijdens runtime in plaats van tijdens het compileren. (Dat is eigenlijk een goede reden om te gebruiken const char * bij het toewijzen van letterlijke tekenreeksen aan een variabele van jou.)


5
2018-03-27 15:37



Nee, er kan niet op worden vertrouwd, maar het opslaan van alleen-lezen stringconstanten in een pool is een vrij eenvoudige en effectieve optimalisatie. Het is gewoon een kwestie van een alfabetische lijst met tekenreeksen opslaan en deze vervolgens aan het einde uitvoeren in het objectbestand. Bedenk hoeveel "\ n" of "" constanten zich in een gemiddeld codebasis bevinden.

Als een compiler extra waanzinnig zou willen worden, zou het ook achtervoegsels kunnen hergebruiken: "\ n" kan worden weergegeven door te wijzen naar het laatste teken van "Hallo \ n". Maar dat komt waarschijnlijk met heel weinig voordelen voor een aanzienlijke toename van de complexiteit.

Hoe dan ook, ik geloof niet dat de standaard iets zegt over waar alles echt is opgeslagen. Dit gaat een heel implementatiespecifiek ding zijn. Als u twee van die declaraties in een afzonderlijk .cpp-bestand plaatst, zullen de dingen waarschijnlijk ook veranderen (tenzij uw compiler significant koppelwerk verricht).


2
2018-03-27 15:21