Ich frage mich nur: Wenn ich einem Zeiger eine Einschränkung hinzufüge, sage ich dem Compiler, dass der Zeiger kein Alias für einen anderen Zeiger ist. Nehmen wir an, ich habe eine Funktion wie:
// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
for (size_t i = 0; i < size; ++i)
{
result [i] = a [0] * b [i];
}
}
Wenn der Compiler davon ausgehen muss result
könnte sich überschneiden mit a
, muss es jedes Mal neu abrufen. Aber a
ist markiert const
könnte der Compiler auch davon ausgehen, dass a fest ist, und daher ist es in Ordnung, es einmal abzurufen.
Die Frage ist, was ist in einer solchen Situation der empfohlene Weg, um mit Restriktionen zu arbeiten? Ich möchte sicher nicht, dass der Compiler neu abgerufen wird a
jedes Mal, aber ich konnte keine guten Informationen darüber finden, wie restrict
soll hier arbeiten.
Ihr Zeiger ist konstant und sagt jedem, der Ihre Funktion aufruft, dass Sie die Daten, auf die durch diese Variable gezeigt wird, nicht berühren werden. Leider weiß der Compiler immer noch nicht, ob result ein Alias der const-Zeiger ist. Sie können immer einen nicht konstanten Zeiger als konstanten Zeiger verwenden. Zum Beispiel nehmen viele Funktionen einen const char (dh String) Zeiger als Parameter, aber Sie können ihm, wenn Sie möchten, einen nicht konstanten Zeiger übergeben, die Funktion gibt Ihnen lediglich das Versprechen, dass sie diesen nicht verwenden wird Zeiger, um etwas zu ändern.
Um Ihrer Frage näher zu kommen, müssten Sie grundsätzlich “restrict” zu a und b hinzufügen, um dem Compiler zu “versprechen”, dass jeder, der diese Funktion verwendet, das Ergebnis nicht als Alias an a oder b weitergibt. Vorausgesetzt natürlich, Sie sind in der Lage, ein solches Versprechen zu geben.
Alle hier wirken sehr verwirrt. Bisher gibt es in keiner Antwort ein einziges Beispiel für einen const-Zeiger.
Die Erklärung const float* a
ist nicht ein konstanter Zeiger, es ist konstanter Speicher. Der Zeiger ist immer noch änderbar. float *const a
ist ein konstanter Zeiger auf einen veränderlichen Float.
Die Frage sollte also lauten, ob es einen Sinn hat float *const restrict a
(oder const float *const restrict a
wenn Sie es vorziehen).
Ja, Sie müssen einschränken. Zeiger auf Konstante bedeutet nicht, dass nichts die Daten ändern kann, nur dass Sie sie nicht durch diesen Zeiger ändern können.
const
ist meistens nur ein Mechanismus, um den Compiler zu bitten, Ihnen dabei zu helfen, den Überblick darüber zu behalten, welche Funktionen Sie ändern dürfen. const
ist kein Versprechen an den Compiler, dass eine Funktion wirklich keine Daten ändert.
nicht wie restrict
mit pointer-to-const
zu veränderlichen Daten ist im Grunde ein Versprechen an andere Menschen, nicht an den Compiler. Wegwerfen const
überall führt nicht zu einem falschen Verhalten des Optimierers (AFAIK), es sei denn, Sie versuchen, etwas zu ändern, das der Compiler in den Nur-Lese-Speicher gestellt hat (siehe unten über static const
Variablen). Wenn der Compiler beim Optimieren die Definition einer Funktion nicht sehen kann, muss er davon ausgehen, dass sie wegwirft const
und ändert Daten durch diesen Zeiger (d.h. dass die Funktion die nicht respektiert const
ness seiner Zeiger args).
Das weiß der Compiler static const int foo = 15;
kann sich jedoch nicht ändern und fügt den Wert zuverlässig ein, selbst wenn Sie seine Adresse an unbekannte Funktionen übergeben. (Deshalb static const int foo = 15;
ist nicht langsamer als #define foo 15
für einen optimierenden Compiler. Gute Compiler optimieren es wie a constexpr
wenn möglich.)
Erinnere dich daran restrict
ist ein Versprechen an den Compiler, dass Dinge, auf die Sie über diesen Zeiger zugreifen, sich mit nichts anderem überschneiden. Wenn das nicht stimmt, wird Ihre Funktion nicht unbedingt das tun, was Sie erwarten. zB nicht anrufen foo_restrict(buf, buf, buf)
vor Ort zu betreiben.
Nach meiner Erfahrung (mit gcc und clang) restrict
ist hauptsächlich bei Zeigern nützlich, die Sie durchspeichern. Es tut nicht weh zu setzen restrict
auch auf Ihre Quellzeiger, aber normalerweise erhalten Sie die größtmögliche Verbesserung, wenn Sie es nur auf die Zielzeiger setzen, wenn alle die Geschäfte, die Ihre Funktion durchführt restrict
Zeiger.
Wenn Sie Funktionsaufrufe in Ihrer Schleife haben, restrict
bei einem Quellzeiger lässt clang (aber nicht gcc) ein Neuladen vermeiden. Sehen diese Testfälle auf dem Godbolt-Compiler-Explorerspeziell diese hier:
void value_only(int); // a function the compiler can't inline
int arg_pointer_valonly(const int *__restrict__ src)
{
// the compiler needs to load `*src` to pass it as a function arg
value_only(*src);
// and then needs it again here to calculate the return value
return 5 + *src; // clang: no reload because of __restrict__
}
gcc6.3 (das auf die x86-64-SysV-ABI abzielt) beschließt, es beizubehalten src
(der Zeiger) in einem vom Aufruf erhaltenen Register über den Funktionsaufruf hinweg und neu laden *src
nach dem Anruf. Entweder haben die Algorithmen von gcc diese Optimierungsmöglichkeit nicht erkannt oder entschieden, dass es sich nicht lohnt, oder die gcc-Entwickler haben sie absichtlich nicht implementiert, weil sie denken, dass sie nicht sicher ist. IDK welche. Aber da Clang es tut, vermute ich, dass es so ist wahrscheinlich legal nach dem C11-Standard.
clang4.0 optimiert dies, um nur zu laden *src
einmal, und halten Sie den Wert in einem vom Aufruf erhaltenen Register über den Funktionsaufruf hinweg. Ohne restrict
tut es dies nicht, da sich die aufgerufene Funktion (als Nebeneffekt) ändern könnte *src
durch einen anderen Zeiger.
Der Aufrufer dieser Funktion könnte beispielsweise die Adresse einer globalen Variablen übergeben haben. Aber jede Änderung von *src
außer durch die src
Zeiger würde das Versprechen verletzen, dass restrict
zum Compiler gemacht. Da wir nicht bestehen src
zu valonly()
kann der Compiler davon ausgehen, dass er den Wert nicht ändert.
Der GNU-Dialekt von C erlaubt die Verwendung __attribute__((pure))
oder __attribute__((const))
zu erklären, dass eine Funktion keine Seiteneffekte hat, wodurch diese Optimierung ohne restrict
, aber es gibt kein portables Äquivalent in ISO C11 (AFAIK). Natürlich ermöglicht auch das Inline-Zulassen der Funktion (indem sie in eine Header-Datei eingefügt oder LTO verwendet wird) diese Art der Optimierung und ist für kleine Funktionen viel besser, insbesondere wenn sie innerhalb von Schleifen aufgerufen werden.
Compiler sind im Allgemeinen ziemlich aggressiv bei der Durchführung von Optimierungen, die der Standard zulässt, auch wenn sie für einige Programmierer überraschend sind und einen vorhandenen unsicheren Code beschädigen, der zufällig funktioniert hat. (C ist so portabel, dass viele Dinge im Basisstandard undefiniertes Verhalten sind; die meisten netten Implementierungen definieren das Verhalten vieler Dinge, die der Standard als UB belässt.) C ist keine Sprache, in der es sicher ist, Code auf den Compiler zu werfen, bis es tut, was Sie wollen, ohne zu überprüfen, ob Sie es richtig machen (ohne vorzeichenbehaftete Ganzzahlüberläufe usw.)
Wenn Sie sich die x86-64-asm-Ausgabe zum Kompilieren Ihrer Funktion (aus der Frage) ansehen, können Sie den Unterschied leicht erkennen. Ich ziehe es an der Godbolt-Compiler-Explorer.
In diesem Fall setzen restrict
an a
reicht aus, um die Last klirren zu lassen a[0]
aber nicht gcc.
Mit float *restrict result
sowohl clang als auch gcc heben die Last.
z.B
# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
vmovss xmm0, DWORD PTR [rsi]
vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L5
vs.
# gcc 6.3 with float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
vmovss xmm1, DWORD PTR [rsi] # outside the loop
.L11:
vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L11
Zusammenfassend also stellen __restrict__
auf alle Zeiger, die garantiert nicht mit etwas anderem überlappen.
Übrigens, restrict
ist nur ein Schlüsselwort in C. Einige C++-Compiler unterstützen __restrict__
oder __restrict
als Erweiterung, so sollten Sie #ifdef
es weg auf unbekannte Compiler.
Seit
In dem C-99-Standard (ISO/IEC 9899:1999 (E)) es gibt beispiele dafür const * restrict
zB in Abschnitt 7.8.2.3:
Die Funktionen strtoimax und strtoumax
Zusammenfassung
#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
char ** restrict endptr, int base);
--- snip ---
Geht man also davon aus, dass die Norm ein solches Beispiel nicht liefern würde, wenn const *
überflüssig waren * restrict
dann sind sie in der Tat nicht überflüssig.
Wie in der vorherigen Antwort angegeben, müssen Sie “restrict” hinzufügen. Ich wollte auch Ihr Szenario kommentieren, dass “Ergebnis sich mit a überschneiden könnte”. Das ist nicht der einzige Grund, warum der Compiler erkennt, dass sich “a” ändern könnte. Es könnte auch von einem anderen Thread geändert werden, der einen Zeiger auf “a” hat. Selbst wenn Ihre Funktion also keine Werte geändert hat, geht der Compiler immer noch davon aus, dass sich “a” ändern könnte.
Leider habe ich derzeit keinen Compiler zur Hand, aber wenn Sie möchten, können Sie die Assembly dafür erstellen (z. B. gcc -S) und sehen, ob sie wirklich a abruft[0] jedes Mal oder nicht.
– Falstro
19. Januar 2009 um 12:47 Uhr
@falstro: Ich habe es gesagt auf Godbolt. Siehe meine Antwort für eine Beschreibung, wie gcc und clang damit umgehen, aber TL: DR-Version: verwenden
float *__restrict__ result
um sie beide gut optimieren zu lassen. clang schafft es auch, die Last mit zu hebenconst float *__restrict a
aber gcc nicht.– Peter Cordes
28. April 2017 um 2:48 Uhr
Du hast die Flagge
C
undC++
aberconst
in C undconst
in C++ haben ganz andere Bedeutungen.– 12431234123412341234123
4. Juli 2017 um 18:07 Uhr
@12431234123412341234123 | Könnten Sie das näher erläutern?
– Noein
5. August 2017 um 12:12 Uhr
@Noein In C,
const
meintread only
die Hardware und andere Prozesse können es möglicherweise noch ändern (Beispiel ist das Ergebnisregister eines ADC, das wäreconst
undvolatile
). In C++,const
bedeutet: Es ändert sich nie, ist nicht dein Prozess, nicht andere Prozesse, nicht Hardware.– 12431234123412341234123
8. August 2017 um 11:18 Uhr