Semantische Unterschiede globaler Definitionen in lokalen Gruppen in Bezug auf Save-Stack-Einträge

Semantische Unterschiede globaler Definitionen in lokalen Gruppen in Bezug auf Save-Stack-Einträge

Dies ist eine Folgefrage zu\globale Variante von \csname…\endcsname, in der nach einer Lösung des dort erläuterten Problems gefragt wird, jedoch nicht nach einer Erklärung.

Der Kern des Problems ist, dass

{ \gdef\foo{...} }

scheint sich anders zu verhalten als

{ \expandafter\gdef\csname foo\endcsname{...} }

wobei Letzteres „einen Halteeintrag zum Save_Stack hinzufügt.“

Deshalb würde ich gerne wissen:

  1. Warum gibt es einen Unterschied im Verhalten dieser beiden Konstrukte? Ist das nur ein Nebeneffekt der Art und Weise \csname...\endcsnameder Implementierung oder handelt es sich um einen beabsichtigten semantischen Unterschied (soweit sich das feststellen lässt).
  2. Was {retaining ...}bedeutet der in der verlinkten Frage erwähnte Eintrag in der Logdatei genau? Warum wäre es notwendig, lokale Definitionen nach der Schließung der Gruppe beizubehalten?

Antwort1

Vielen Dank für diese Frage. Ich habe das Beispiel nicht ganz verstanden.die andere Frageals ich es las, aber diese Frage motivierte mich, es mir noch einmal anzusehen, und ich glaube, ich verstehe es jetzt.

Was aus dem Beispiel im Wesentlichen nicht klar wurde, ist, dass das Problem dann auftritt, wenn wir mehrere solcher \expandafter\gdef\csname foo\endcsname{...}Konstruktionen innerhalb desDasselbeGruppe (oder in dieser Gruppe verschachtelte Gruppen), d. h. der Speicherstapel wächst nur, solange man sich innerhalb der Gruppe befindet. Die Meldung „Retaining…“ wird beim Verlassen der Gruppe ausgegeben (wenn der Stapel gelöscht wird), aber diese Meldung stellt selbst kein Problem dar; sie dient nur als Beweis dafür, dass der Stapel zuvor gewachsen ist.

Dieser Absatz war wahrscheinlich verwirrend, also wollen wir den Speicherstapel von Grund auf verstehen. :-)


1.Betrachten Sie dieses Beispiel:

\def\a{hello}
{
    \def\a{world}
}

Wenn TeX in der inneren Gruppe hier sieht, \adass neu definiert wird (auf world), speichert es den vorherigen Wert (einen Verweis auf eine Tokenliste, die enthält hello) auf dem Speicherstapel. Wenn es dann das Ende der Gruppe erreicht, wird der Stapel gelöscht, um die Definition von wiederherzustellen hello. Dies ist der offensichtliche Grund für den Speicherstapel, und das Folgende ist die entsprechende Ablaufverfolgungsausgabe, vorausgesetzt, Sie haben \tracingrestores=2und \tracinggroups=2und außerdem \tracingassigns=2und dass die innere Gruppe in Zeile 10 (sagen wir) beginnt – außerdem habe ich die Ausgabe geändert, um den Zeilenumbruch vor zu entfernen {into…}:

{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}

2.Betrachten wir nun das gleiche Beispiel.ohnedas \def\a{hello}oben:

{
    \def\a{world}
}

– Wenn TeX \def\a{world}innerhalb der Gruppe sieht, muss es \aerneut die vorherige Bedeutung von speichern. Sie war zufällig undefiniert, aber wir müssen sie nach dem Verlassen der Gruppe wieder undefiniert haben, also muss TeX die „undefinierte“ Bedeutung auf den Speicherstapel legen. Die Ablaufverfolgungsausgabe lautet:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}

3.Betrachten wir nun ein ähnliches Beispiel:

{
  \let\a=\relax
  \gdef\a{world}
}

Wenn TeX hier das erste sieht \let\a=\relax, muss es die vorherige Bedeutung („undefiniert“) auf dem Speicherstapel speichern, genau wie im vorherigen Beispiel. Wenn es dann das sieht, \gdefmuss es nichts auf den Speicherstapel legen. Wenn es schließlich das Ende der Gruppe erreicht und beginnt, seinen Stapel zu leeren (der derzeit die „undefinierte“ Bedeutung für enthält \a), bemerkt es, dass \ajetzt eine globale Definition hat, also ignoriert es die „undefinierte“ Bedeutung und behält die globale Definition bei. Dies erklärt die Ablaufverfolgungsausgabe:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

4.Betrachten Sie abschließend dieses Beispiel:

{
  \expandafter\gdef\csname a\endcsname{world}
}

Dies stellt sich als genau dasselbe wie im vorherigen Fall heraus. Dies liegt daran, dass TeX, wenn es das sieht, \expandafterdas vorübergehend übergeht \gdefund mit der Arbeit am nächsten Token ( \csname) beginnt. Dies bedeutet, dass auf gearbeitet wird , \csname a\endcsnameum das Makro zu erstellen \a, mit einer Definition von , \relaxwenn es nicht bereits definiert wurde (so \csnamefunktioniert es einfach), und erst danach wird auf das (zuvor vorübergehend übergangene) gearbeitet \gdefund \adie neue Definition, die folgt ( world), neu definiert. Daher ist die Ablaufverfolgungsausgabe dieselbe wie beim letzten Mal:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

Das ist alles. Um Ihre spezifischen Fragen zu beantworten:

  • (1) Es ist nur ein Nebeneffekt der Art und Weise, wie \csname … \endcsnamees implementiert wird; nämlich es \letist das Token für \relaxden Ersten. Dies ist jedoch gut dokumentiert und daher wohl auch Teil der Semantik (wie jeder es erwartet).

  • (2a) Der {retaining ...}Eintrag in der Protokolldatei bedeutet (siehe Seite 301 desDas TeXbook) dass diefrüherDie auf den Speicherstapel abgelegte Definition, nämlich der Wert des Makros, bevor es \relaxvon auf gesetzt wurde \csname … \endcsname, wurde aufgrund von ignoriert \gdef(und die globale Definition wurde beibehalten).

  • (2b) „Warum sollte es notwendig sein, lokale Definitionen beizubehalten, nachdem die Gruppe geschlossen wurde?“ — Das ist nicht der Fall, und sie werden nicht beibehalten. Stattdessen sieht man, dassWannDie Gruppe wird geschlossen. Alle Definitionen, die zuvor aufgrund lokaler Definitionen innerhalb derselben Gruppe gespeichert wurden, werden nun überprüft. Wenn es irgendwann eine globale Definition gab, wird diese erst jetzt verworfen. Am Ende der Gruppe ist der Speicherstapel leer (oder hat besser gesagt dieselbe Größe wie beim Betreten der Gruppe).


Genauer gesagt, hier ist ein Beispiel für das Problem und Lösungen aufdiese Frage. Der Fragesteller hat viele Makros (im Wesentlichen) innerhalb einer Gruppe definiert, und zwar auf eine Art und Weise, die ungefähr folgendem entspricht:

{
    \expandafter\gdef\csname A\endcsname{I'm A}
    \expandafter\gdef\csname B\endcsname{I'm B}
    \expandafter\gdef\csname C\endcsname{I'm C}
}

und so weiter. Wie wir in den Beispielen 3 und 4 oben gesehen haben, ist dies gleichbedeutend mit:

{
    \let\A=\relax \gdef\A{I'm A}
    \let\B=\relax \gdef\B{I'm B}
    \let\C=\relax \gdef\C{I'm C}
}

und so weiter. Somit legt jede der obigen Definitionen einen Eintrag auf den Speicherstapel (für die Bedeutung, bevor der Name der Steuersequenz von angegeben \letwurde ), und erst am Ende der Gruppe werden alle diese Einträge entfernt. Wenn es also zu viele solcher Definitionen gibt, wird Ihnen die „Speicherkapazität“ ausgehen.\relax\csname ... \endcsname

Dererste gepostete Antwort(von Steven B. Segletes) schlug vor, das Äquivalent dazu zu tun, jede \csname … \endcsnameAktion auf der obersten Ebene auszuführen (wo nichts auf den Speicherstapel gelegt wird).

Derzweite gepostete Antwort(von Marcel Krüger) schlug vor, das Äquivalent von Folgendem zu tun:

{
  \begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}

wobei die Definitionen innerhalb von Gruppen erfolgen, die sofort beendet werden, sodass jeder Stapel sofort geleert wird: Die Ablaufverfolgungsausgabe lautet (Modulo-Zeilenumbrüche):

{entering simple group (level 1) at line 10}

{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}

{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}

{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}

{leaving simple group (level 1) entered at line 10}

Wenn Sie sich nicht für Erweiterbarkeit usw. interessieren und nur versuchen, den Speicherstapel zu verstehen, dann sieht die vorgeschlagene Lösung wie folgt aus:

{
    {\let\A=\relax} \gdef\A{I'm A}
    {\let\B=\relax} \gdef\B{I'm B}
    {\let\C=\relax} \gdef\C{I'm C}
}

damit Sie sehen, warum der Speicherstapel nicht weiter wächst.

verwandte Informationen