[C++] Pointer-waarde verandert niet tenzij print?

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
Hoi, ik zit al een uurtje met mijn hoofd tegen het toetsenbord te rammen.

Ik probeer een variabele een andere waarde te geven in een andere functie. Triviaal - maar lukt niet.

Code waar het niet lukt:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ScriptUtils.cpp
namespace {
    int notificationHandle = 0; // Dit moet veranderen
}

void UI::Notify(const std::string& message) {
    logger.Write(DEBUG, "Notif with %d", notificationHandle); // Is altijd 0?
    showNotification(fmt::format("{}\n{}", Constants::NotificationPrefix, message), &notificationHandle); // Geeft pointertje door
}

// UIUtils.h
void showNotification(const std::string &message, int *prevNotification);

// UIUtils.cpp
void showNotification(const std::string &message, int *prevNotification) {
    // gebruikt pointertje om oud bericht te verwijderen
    if (prevNotification != nullptr && *prevNotification != 0) {
        UI::_REMOVE_NOTIFICATION(*prevNotification);
    }
    UI::_SET_NOTIFICATION_TEXT_ENTRY("STRING");

    UI::ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME((char*)message.c_str());

    // id van nieuw bericht wordt gezet in notificationHandle via prevNotification ptr
    int id = UI::_DRAW_NOTIFICATION(false, false);
    if (prevNotification != nullptr) {
        // Logs geven hier non-0 aan voor beide kanten
        *prevNotification = id;
    }
}


De enige verandering nodig, zodat het werkt:
C++:
1
    logger.Write(DEBUG, "Notif with %d @ %p", notificationHandle, &notificationHandle);


Zodra ik het adres van notificationHandle print, werkt het als een tierelier. Welke optimalisatie zit mij te dwarsbomen?

Dit is voor een x64 library gebouwd met Visual Studio 2019, C++17, /O2 optimalisatie, /Oi intrinsic functions. Verder standaard x64 release instellingen.

Mijn Google-fu levert weinig op. Ik denk dat de compiler slim probeert te zijn en het wegoptimaliseert. Het zou fijn zijn als ik de compiler op een of andere manier kan vertellen dat dit niet oké is.

Alternatieve oplossingen bestaan (geef reference door, maar dan moet ik een overload maken waar het oude bericht niet wordt verwijderd), maar liever wil ik een magisch keywordje hebben.

Iemand die hier soep van kan maken?

Alle reacties


Acties:
  • 0 Henk 'm!

  • Sissors
  • Registratie: Mei 2005
  • Niet online
Ik weet niet hoe deze functies worden aangeroepen, en ben meer bekend met embedded, maar wat gebeurd er als je hem als volatile int definieert?

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
Sissors schreef op zaterdag 7 september 2019 @ 09:28:
Ik weet niet hoe deze functies worden aangeroepen, en ben meer bekend met embedded, maar wat gebeurd er als je hem als volatile int definieert?
Het resultaat is hetzelfde als ik het adres van het ding meegeef als int*.

Simpelweg een pointer expliciet toevoegen helpt dan weer wel:

C++:
1
2
    int* notifHandleAddr = &notificationHandle;
    showNotification(fmt::format("{}\n{}", Constants::NotificationPrefix, message), notifHandleAddr);


Vreemd.

Acties:
  • 0 Henk 'm!

  • venom1
  • Registratie: Mei 2011
  • Laatst online: 20-10-2024
Kun je de handle niet een variable in je class UI maken? Als test

Acties:
  • 0 Henk 'm!

  • Sissors
  • Registratie: Mei 2005
  • Niet online
ikt schreef op zaterdag 7 september 2019 @ 09:34:
[...]


Het resultaat is hetzelfde als ik het adres van het ding meegeef als int*.

Simpelweg een pointer expliciet toevoegen helpt dan weer wel:

C++:
1
2
    int* notifHandleAddr = &notificationHandle;
    showNotification(fmt::format("{}\n{}", Constants::NotificationPrefix, message), notifHandleAddr);


Vreemd.
Of ik begrijp je nu niet goed, of jij begreep mij niet goed. Maar wat gebeurd er als je notificationHandle als een volatile int definieert ipv reguliere int.

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
Waar wordt de notificationHandle effectief gelezen? (Behalve as je 'em print met logger.Write)?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • dogtagz
  • Registratie: December 2014
  • Laatst online: 03-10 14:26
Moet je niet in de functie parameters een reference meegeven van notificationhandle in plaats van een pointer (hetzelfde wat je doet bij const string &message)?

Vervolgens probeer je te zeggen dat het adres een specifieke waarde is, in plaats van de waarde van het adres?

Wat is uberhaupt de reden waarom je een pointer wilt meegeven ipv een reference?

$ alias cd='rm -rf'


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
dogtagz schreef op zondag 8 september 2019 @ 07:41:
Moet je niet in de functie parameters een reference meegeven van notificationhandle in plaats van een pointer (hetzelfde wat je doet bij const string &message)?

Vervolgens probeer je te zeggen dat het adres een specifieke waarde is, in plaats van de waarde van het adres?

Wat is uberhaupt de reden waarom je een pointer wilt meegeven ipv een reference?
Een pointer omdat het ook een nullptr mag zijn, waar het vorige bericht dan niet wordt verwijderd. Je ziet ook dat ik de waarde waar de pointer naar wijst verander.

Dit werkt gewoon zolang de compiler de handle niet wegoptimaliseert.
farlane schreef op zondag 8 september 2019 @ 00:00:
Waar wordt de notificationHandle effectief gelezen? (Behalve as je 'em print met logger.Write)?
Ik geef het door als pointer, en die pointer wordt gedereferenced.
Sissors schreef op zaterdag 7 september 2019 @ 10:42:
[...]

Of ik begrijp je nu niet goed, of jij begreep mij niet goed. Maar wat gebeurd er als je notificationHandle als een volatile int definieert ipv reguliere int.
Heb het onhandig uitgelegd - de functie accepteert een pointer naar int, dus als ik notificatie handle declareer als volatile int, accepteert de compiler dit niet als ik simpelweg &notificationHandle doe, gezien het dan een volatile int* is. Ik moet dan of de functie aanpassen dat het een volatile int ptr accepteert, of de volatile int pointer casten naar een normale int ptr.

Ik had dus het laatste geprobeerd, wat niet werkte.
venom1 schreef op zaterdag 7 september 2019 @ 10:15:
Kun je de handle niet een variable in je class UI maken? Als test
UI is een namespace. Ik kan zeker een klasse maken, maar dan wordt het onhandig in gebruik. Ik vind het ook overkill om dat te doen om een onhandige compileroptimalisatie te omzeilen...

Acties:
  • 0 Henk 'm!

  • dogtagz
  • Registratie: December 2014
  • Laatst online: 03-10 14:26
Hmm lastig, ik ben zelf ook nog enorm lerend in C++ maar ik probeerde te helpen ;)

Wat je omschrijft, hoort inderdaad ook te werken. Maar values weg optimaliseren? Is dat een ding?

Misschien kan je wat unit tests schrijven voor die functies en vanuit daar debuggen?

Ik ben benieuwd naar wat de oplossing is.

$ alias cd='rm -rf'


Acties:
  • +1 Henk 'm!

  • kh65
  • Registratie: Juni 2006
  • Laatst online: 28-08 12:41
In je code wordt nu het veld notificationHandle helemaal niet gezet.
Ik weet niet of je dat ergens anders wel doet natuurlijk, maar dat zou bijv. de reden kunnen zijn
dat de compiler het veld weg-optimaliseert.

Volgens mij is het good practice om in zo'n util class ook een functie te maken om
de notificationHandle een waarde te geven, en die aan te roepen...?

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
kh65 schreef op zondag 8 september 2019 @ 09:55:
In je code wordt nu het veld notificationHandle helemaal niet gezet.
Ik weet niet of je dat ergens anders wel doet natuurlijk, maar dat zou bijv. de reden kunnen zijn
dat de compiler het veld weg-optimaliseert.

Volgens mij is het good practice om in zo'n util class ook een functie te maken om
de notificationHandle een waarde te geven, en die aan te roepen...?
notificationHandle wordt bij aanmaken gezet op 0. Wel een interessant idee, ik probeer als ik thuis ben andere manieren om te initializen.

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
ikt schreef op zondag 8 september 2019 @ 09:07:
[...]
Ik geef het door als pointer, en die pointer wordt gedereferenced.
Ik zie dat je notificationHandle een waarde geeft dmv van de pointer maar waar gebruik je de waarde van notificationHandle behalve in de log functies (Die zouden weg kunnen zijn in release)?

Daarnaast, levert int id = UI::_DRAW_NOTIFICATION(false, false); wel eens iets op wat niet 0 is?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • +1 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
farlane schreef op zondag 8 september 2019 @ 21:46:
[...]


Ik zie dat je notificationHandle een waarde geeft dmv van de pointer maar waar gebruik je de waarde van notificationHandle behalve in de log functies (Die zouden weg kunnen zijn in release)?

Daarnaast, levert int id = UI::_DRAW_NOTIFICATION(false, false); wel eens iets op wat niet 0 is?
prevNotification krijgt een nullptr-check, wordt gedereferenced en de handle wordt gestopt in UI::_REMOVE_NOTIFICATION.

UI::_DRAW_NOTIFICATION geeft altijd een waarde terug die niet 0 is.

Hij wordt dus wel degelijk gelezen en geschreven. Alleen denkt de compiler daar dus anders over.

Lees ook mijn tweede post, expliciet de pointer naar notificationHandle maken en dát dan doorgeven werkt wel.

Overigens kan ik het niet herproduceren in een standalone project met dezelfde opties, wat ook best raar is. Het kan best een randgevalletje MSVC Compiler zijn...

Om duidelijk te maken: Ik ben vooral benieuwd naar wat het precies veroorzaakt. Workarounds kan ik zelf prima zelf bedenken.

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
ikt schreef op zondag 8 september 2019 @ 21:54:
[...]
prevNotification krijgt een nullptr-check, wordt gedereferenced en de handle wordt gestopt in UI::_REMOVE_NOTIFICATION.

UI::_DRAW_NOTIFICATION geeft altijd een waarde terug die niet 0 is.

Hij wordt dus wel degelijk gelezen en geschreven. Alleen denkt de compiler daar dus anders over.
Een probleem is dat niet duidelijk is wat _REMOVE_NOTIFICATION doet. Als dit compileert naar iets wat niets doet met *prevNotification wordt het alsnog weggeoptimaliseerd waarschijnlijk.
Lees ook mijn tweede post, expliciet de pointer naar notificationHandle maken en dát dan doorgeven werkt wel.
Dat heb ik gezien, ik heb daar niet een verklaring voor. Toch denk ik dat je ergens een bug hebt zitten wat voor dit gedrag zorgt.
Overigens kan ik het niet herproduceren in een standalone project met dezelfde opties, wat ook best raar is.
Project setttings? Macro's?
Het kan best een randgevalletje MSVC Compiler zijn...
Dat kan natuurlijk, maar de kans dat het aan jezelf ligt is groter denk ik.
Om duidelijk te maken: Ik ben vooral benieuwd naar wat het precies veroorzaakt. Workarounds kan ik zelf prima zelf bedenken.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Strikt genomen is na "_REMOVE_NOTIFICATION" alles toegestaan. _[A-Z] is gereserveerd voor macro's en andere identifiers van de C++ implementatie zelf.

Een mogelijke reden waarom zo'n globale verrassend wordt weggeoptimaliseerd is vanwege een compiler die ziet dat je code die veriable nooit legitiem kan gebruiken. Dat is bijvoorbeeld het geval als de compiler kan aantonen dat als je'm gebruikt, dat de veriabele dan ongeïnitialiseerd is. Maar als je een functie foo(&variabele) aanroept, dan zou foo die variabele kunnen initialiseren, dus na die functie-aanroep moet de optimizer aannemen dat de variabele mogelijk geinitialiseerd is.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
farlane schreef op maandag 9 september 2019 @ 08:31:
[...]

Een probleem is dat niet duidelijk is wat _REMOVE_NOTIFICATION doet. Als dit compileert naar iets wat niets doet met *prevNotification wordt het alsnog weggeoptimaliseerd waarschijnlijk.


[...]

Dat heb ik gezien, ik heb daar niet een verklaring voor. Toch denk ik dat je ergens een bug hebt zitten wat voor dit gedrag zorgt.


[...]

Project setttings? Macro's?


[...]

Dat kan natuurlijk, maar de kans dat het aan jezelf ligt is groter denk ik.


[...]
_REMOVE_NOTIFICATION is een functie die als het ware een vertaalslag is van een interne scripting taal naar een C++ interface, aan de hand van een functie-hash.

C++:
1
2
// zit in namespace UI
static void _REMOVE_NOTIFICATION(int notificationId) { invoke<Void>(0xBE4390CB40B3E627, notificationId); }


Origineel komt invoke van de ScriptHookV SDK, maar daar heb ik het volgende van gemaakt:

C++:
1
2
3
4
5
6
7
8
9
template <typename R, class ... Args>
static inline R invoke(UINT64 hash, Args&& ... args)
{
    nativeInit(hash);

    (nativePush(std::forward<Args>(args)), ...);

    return *reinterpret_cast<R*>(nativeCall());
}


Gezien dit voor letterlijk alle andere native-calls goed werkt en er bij het printen binnen mijn showNotification functie wel duidelijk is dat er gewoon een int uit de dereference-int operatie rolt, betwijfel ik of dit een rol speelt.

Het eindresultaat van die functie is dat de notificatie met dat ID wordt verwijderd van het scherm.

Ik hoop dat ik een bug heb veroorzaakt. Dat geweldig zijn, want dan is dat op te lossen :+

Verder verschillen met het testproject en mijn project is dat ik maar deels die library-laag heb nagemaakt. Het hele scripting-engine-hook gebeuren en daar een interface voor maken om zo los de script-API na te bootsen, is toch een stapje te ver voor me :p
MSalters schreef op dinsdag 10 september 2019 @ 17:42:
Strikt genomen is na "_REMOVE_NOTIFICATION" alles toegestaan. _[A-Z] is gereserveerd voor macro's en andere identifiers van de C++ implementatie zelf.

Een mogelijke reden waarom zo'n globale verrassend wordt weggeoptimaliseerd is vanwege een compiler die ziet dat je code die veriable nooit legitiem kan gebruiken. Dat is bijvoorbeeld het geval als de compiler kan aantonen dat als je'm gebruikt, dat de veriabele dan ongeïnitialiseerd is. Maar als je een functie foo(&variabele) aanroept, dan zou foo die variabele kunnen initialiseren, dus na die functie-aanroep moet de optimizer aannemen dat de variabele mogelijk geinitialiseerd is.
UI::_REMOVE_NOTIFICATION is geen macro, het ding heet gewoon zo omdat de library-maker dat zo heeft genoemd.

Voor contextvorming - de code wordt in een projectje gebruikt dat tegen verschillende dingen aan praat, maar het stuk dat problematisch is lijkt me geïsoleerd genoeg.

Mijn showNotification implementatie
Mijn gebruik van mijn showNotification in Notify

En het rare:
C++:
1
int* notifHandleAddr = &notificationHandle;


Dit toevoegen aan Notify zorgt ervoor dat de notificationHandle wél door showNotification wordt bewerkt.

Note: Voorheen gebruikte ik showNotification overal met als notificationHandle als globale variabele (extern gedeclareerd in andere compilation units buiten script.cpp), wat altijd wel het gewenste gedrag liet zien.

Acties:
  • 0 Henk 'm!

  • DroogKloot
  • Registratie: Februari 2001
  • Niet online

DroogKloot

depenisvanjezus

Ik vermoed dat de anonymous namespace om notificationHandle (zorgt voor internal linkage) hier iets mee te maken heeft, want onder gcc met UI::_DRAW_NOTIFICATION vervangen door random() kan ik dit ook zonder temporary int* notifHandleAddr in UI::Notify niet reproduceren. Wat gebeurt er als je de namespace simpelweg een naam geeft?

[ Voor 15% gewijzigd door DroogKloot op 10-09-2019 20:49 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
ikt schreef op dinsdag 10 september 2019 @ 20:13:
UI::_REMOVE_NOTIFICATION is geen macro, het ding heet gewoon zo omdat de library-maker dat zo heeft genoemd.
En dat is dus het punt. Dat mag die library maker niet. Je compiler mag er alsnog een macro van maken, ook al had die library-maker andere ideeën.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
Hmm, ik kan eerlijk gezegd niet overzien wat er gebeurt. Het frappante is wel dat in de module waar je int gedefinieerd staat deze static is en verder niet gebruikt wordt (behalve dan het adres in de call naar een functie in een andere module)

Leuk probleem overigens....

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
De variable uit de anonymous namespace halen en global maken: Geen verschil.
De variable een static int maken in de functie: Geen verschil.
Naamgeving _<hoofdletter> natives naar iets anders noemen: Geen verschil.
Het adres van het ding een keertje expliciet opvragen: Effectief.

Achja, dan maar die workaround. Blijf het raar vinden.

  • _Tygernoot
  • Registratie: December 2009
  • Laatst online: 11:02
Ik ben niet zeker, maar het moet volgens mij toch wel door de unnamed namespace komen.

Unnamed namespaces hebben internal linkage. Als gevolg is de notificationHandle enkel gedeclareerd binnen ScriptUtils.cpp. Omdat je nu een referentie van die variabele doorgeeft aan een functie in een andere translation unit... heb ik eigenlijk geen flauw benul wat er nu gebeurt haha. Is het een nullptr in showNotification()?

Kan me inbeelden dat de compiler dan "whatever" zegt. Ben eigenlijk benieuwd waarom dit niet eerder een error zou geven. Maar goed, is misschien wel een reden voor, of wie weet geeft g++ hier weer wat anders als resultaat :p

Met je workaround maak je een nieuwe lokale variable aan:
int* notifHandleAddr = &notificationHandle; // prevents optimizing away the pointery thing

Die pointer is dan wel accessible in showNotification(), want die staat op de stack in the Notify() functie, en niet in een unnamed namespace.

Wat me dan wel weer verbaast, is dat je zegt dat dit eerst doen wél werkt:
logger.Write(DEBUG, "Notif with %d @ %p", notificationHandle, &notificationHandle);

Geen idee hoe dat nu weer kan, dat gaat me allemaal mijn kennis een beetje te boven haha. Ik zou de assembly code eens bekijken of er dan iets lokaals op de stack gekwakt wordt of iets dergelijks.

Acties:
  • +1 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
De unnamed namespace beperkt de visibility van de variabele, niet de lifetime. Ik kan dus prima een pointer naar het ding doorgeven naar een andere translation unit.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • _Tygernoot
  • Registratie: December 2009
  • Laatst online: 11:02
Maar om een of andere reden niet een directe pointer naar de variabele (blijkt)? Nou ja, dat was mijn gedachte in elk geval... :)

Even lokaal testje geprobeerd met g++, en daar werkt het idd wel.

Zijn er MS support forums om dit eens te posten? Ben nu toch wel benieuwd :P

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:00
_Tygernoot schreef op donderdag 26 september 2019 @ 22:39:
Maar om een of andere reden niet een directe pointer naar de variabele (blijkt)? Nou ja, dat was mijn gedachte in elk geval... :)

Even lokaal testje geprobeerd met g++, en daar werkt het idd wel.

Zijn er MS support forums om dit eens te posten? Ben nu toch wel benieuwd :P
Volgens mij had ik al een testje gedaan waar ik het ding als global opsla buiten de anonymous namespace, maar daar zag ik hetzelfde.

Het lukt me ook niet om de situatie minimaal na te bootsen, dus dan kan Microsoft er ook weinig mee :p
Pagina: 1