Was bedeutet das Schlüsselwort "Einschränken" in C ++?


182

Ich war mir immer unsicher, was das Restriktionsschlüsselwort in C ++ bedeutet.

Bedeutet dies, dass sich die zwei oder mehr Zeiger, die der Funktion zugewiesen wurden, nicht überlappen? Was bedeutet es sonst noch?


23
restrictist ein c99-Schlüsselwort. Ja, Rpbert S. Barnes, ich weiß, dass die meisten Compiler dies unterstützen __restrict__. Sie werden feststellen, dass alles mit doppelten Unterstrichen per Definition implementierungsspezifisch und somit NICHT C ++ ist , sondern eine compilerspezifische Version davon.
KitsuneYMG

5
Was? Nur weil es implementierungsspezifisch ist, ist es nicht C ++. Das C ++ erlaubt explizit die Implementierung spezifischer Dinge und verbietet es nicht oder rendert es nicht C ++.
Alice

4
@Alice KitsuneYMG bedeutet, dass es nicht Teil von ISO C ++ ist und stattdessen als C ++ - Erweiterung betrachtet wird. Compiler-Ersteller dürfen ihre eigenen Erweiterungen erstellen und verteilen, die mit ISO C ++ koexistieren und als Teil einer normalerweise weniger oder nicht portierbaren inoffiziellen Ergänzung zu C ++ fungieren. Beispiele wären das alte Managed C ++ von MS und die neuere C ++ / CLI. Andere Beispiele wären Präprozessoranweisungen und Makros, die von einigen Compilern bereitgestellt werden, wie z. B. die allgemeine #warningAnweisung oder die Funktionssignaturmakros ( __PRETTY_FUNCTION__in GCC, __FUNCSIG__in MSVC usw.).
Justin Time - Wiedereinsetzung Monica

4
@Alice Meines Wissens erfordert C ++ 11 weder die vollständige Unterstützung für C99 noch C ++ 14 oder das, was ich über C ++ 17 weiß. restrictwird nicht als C ++ - Schlüsselwort betrachtet (siehe en.cppreference.com/w/cpp/keyword ) und ist die einzige Erwähnung restrictim C ++ 11-Standard (siehe open-std.org/jtc1/sc22/wg21) /docs/papers/2012/n3337.pdf , eine Kopie des FDIS mit geringfügigen redaktionellen Änderungen, §17.2 [library.c], PDF-Seite 413) besagt:
Justin Time - Wiedereinsetzung von Monica am

4
@ Alice Wie so? Ich erklärte den Teil, der sagt , dass restrictwerden soll , von weggelassen (ausgenommen von links aus) C - Standardbibliothek Funktionssignaturen und Semantik , wenn diese Funktionen in der C ++ Standardbibliothek enthalten sind. Mit anderen Worten, ich habe die Tatsache angegeben, dass restrictdas restrictSchlüsselwort aus der Signatur des C ++ - Äquivalents entfernt werden muss , wenn die Signatur einer C-Standardbibliotheksfunktion in C enthalten ist .
Justin Time - Wiedereinsetzung Monica

Antworten:


141

In seinem Artikel Memory Optimization sagt Christer Ericson, dass er zwar restrictnoch nicht Teil des C ++ - Standards ist, aber von vielen Compilern unterstützt wird, und er empfiehlt, ihn zu verwenden, wenn er verfügbar ist:

Schlüsselwort einschränken

! Neu im ANSI / ISO C-Standard von 1999

! Noch nicht im C ++ - Standard, aber von vielen C ++ - Compilern unterstützt

! Nur ein Hinweis, kann also nichts tun und trotzdem konform sein

Ein Zeiger (oder eine Referenz) mit eingeschränkter Qualifikation ...

! ... ist im Grunde ein Versprechen an den Compiler, dass für den Umfang des Zeigers auf das Ziel des Zeigers nur über diesen Zeiger zugegriffen wird (und Zeiger von ihm kopiert werden).

In C ++ - Compilern, die dies unterstützen, sollte es sich wahrscheinlich genauso verhalten wie in C.

Weitere Informationen finden Sie in diesem SO-Beitrag: Realistische Verwendung des C99-Schlüsselworts "einschränken"?

Nehmen Sie sich eine halbe Stunde Zeit, um Ericsons Zeitung zu lesen. Es ist interessant und die Zeit wert.

Bearbeiten

Ich fand auch heraus, dass der AIX C / C ++ - Compiler von__restrict__ IBM das Schlüsselwort unterstützt .

g ++ scheint dies ebenfalls zu unterstützen, da das folgende Programm unter g ++ sauber kompiliert wird:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

Ich habe auch einen schönen Artikel über die Verwendung von gefunden restrict:

Das Restrict-Schlüsselwort entmystifizieren

Edit2

Ich bin auf einen Artikel gestoßen, in dem speziell die Verwendung von Restrict in C ++ - Programmen beschrieben wird:

Load-Hit-Stores und das Schlüsselwort __restrict

Microsoft Visual C ++ unterstützt auch das __restrictSchlüsselwort .


2
Der Papierlink zur Speicheroptimierung ist tot. Hier ist ein Link zum Audio aus seiner GDC-Präsentation. gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael: Wenn Sie es in einem tragbaren C ++ - Projekt verwenden möchten, sollten Sie dies #ifndef __GNUC__ #define __restrict__ /* no-op */oder ähnliches tun . Und definieren Sie es, __restrictwenn _MSC_VERdefiniert ist.
Peter Cordes

95

Wie andere sagten, wenn ab C ++ 14 nichts bedeutet , betrachten wir die __restrict__GCC-Erweiterung, die dasselbe tut wie die C99 restrict.

C99

restrictsagt, dass zwei Zeiger nicht auf überlappende Speicherbereiche zeigen können. Die häufigste Verwendung sind Funktionsargumente.

Dies schränkt den Aufruf der Funktion ein, ermöglicht jedoch weitere Kompilierungsoptimierungen.

Wenn der Anrufer dem restrictVertrag nicht folgt , undefiniertes Verhalten.

Der C99 N1256 Entwurf 6.7.3 / 7 "Typqualifizierer" sagt:

Die beabsichtigte Verwendung des Einschränkungsqualifizierers (wie der Registerspeicherklasse) besteht darin, die Optimierung zu fördern, und das Löschen aller Instanzen des Qualifizierers aus allen vorverarbeitenden Übersetzungseinheiten, aus denen ein konformes Programm besteht, ändert seine Bedeutung nicht (dh das beobachtbare Verhalten).

und 6.7.3.1 "Formale Definition von Beschränkung" gibt die blutigen Details an.

Eine mögliche Optimierung

Das Wikipedia-Beispiel ist sehr aufschlussreich.

Es zeigt deutlich, wie eine Montageanweisung gespeichert werden kann .

Ohne Einschränkung:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Pseudo-Assemblierung:

load R1  *x    ; Load the value of x pointer
load R2  *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2  *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

Mit Einschränkung:

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x);

Pseudo-Assemblierung:

load R1  *x
load R2  *a
add R2 += R1
set R2  *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1  *x
load R2  *b
add R2 += R1
set R2  *b

Macht GCC das wirklich?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

Mit -O0sind sie gleich.

Mit -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

Für die Uneingeweihten lautet die aufrufende Konvention :

  • rdi = erster Parameter
  • rsi = zweiter Parameter
  • rdx = dritter Parameter

Die GCC-Ausgabe war noch deutlicher als der Wiki-Artikel: 4 Anweisungen gegen 3 Anweisungen.

Arrays

Bisher haben wir Einsparungen bei einzelnen Anweisungen, aber wenn Zeiger Arrays darstellen, die durchlaufen werden sollen, ein häufiger Anwendungsfall, dann könnte eine Reihe von Anweisungen gespeichert werden, wie von Supercat und Michael erwähnt .

Betrachten Sie zum Beispiel:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

Wegen restrict, ein Smart - Compiler (oder Menschen), könnten diese optimieren:

memset(p1, 4, size);
memset(p2, 9, size);

Was ist möglicherweise viel effizienter, da es für eine anständige libc-Implementierung (wie glibc) Assembly-optimiert werden kann ? Ist es in Bezug auf die Leistung besser, std :: memcpy () oder std :: copy () zu verwenden? , möglicherweise mit SIMD-Anweisungen .

Ohne Einschränkung könnte diese Optimierung nicht durchgeführt werden, z. B. berücksichtigen Sie:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Dann formacht die Version:

p1 == {4, 4, 4, 9}

während die memsetVersion macht:

p1 == {4, 9, 9, 9}

Macht GCC das wirklich?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

Mit -O0 sind beide gleich.

Mit -O3:

  • mit einschränken:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    Zwei memsetAnrufe wie erwartet.

  • ohne Einschränkung: keine stdlib-Aufrufe, nur eine 16 Iterationen breite Schleife, die ich hier nicht reproduzieren möchte :-)

Ich hatte nicht die Geduld, sie zu vergleichen, aber ich glaube, dass die eingeschränkte Version schneller sein wird.

Strikte Aliasing-Regel

Das restrictSchlüsselwort wirkt sich nur auf Zeiger kompatibler Typen aus (z. B. zwei int*), da die strengen Aliasing-Regeln besagen, dass das Aliasing inkompatibler Typen standardmäßig ein undefiniertes Verhalten ist. Compiler können daher davon ausgehen, dass dies nicht der Fall ist, und optimieren.

Siehe: Was ist die strenge Aliasing-Regel?

Funktioniert es für Referenzen?

Laut den GCC-Dokumenten gilt Folgendes: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html mit Syntax:

int &__restrict__ rref

Es gibt sogar eine Version für thisMitgliedsfunktionen:

void T::fn () __restrict__

nette Antwort. Was ist, wenn striktes Aliasing deaktiviert ist -fno-strict-aliasing, restrictsollte es keinen Unterschied zwischen Zeigern desselben Typs oder verschiedener Typen machen, nein? (Ich beziehe mich auf "Das Schlüsselwort einschränken betrifft nur Zeiger kompatibler Typen")
idclev 463035818

@ tobi303 Ich weiß es nicht! Lass es mich wissen, wenn du es sicher
herausfindest ;-)

@jww ja, das ist eine bessere Art, es zu formulieren. Aktualisiert.
Ciro Santilli 法轮功 冠状 病 六四 事件 25

restrictbedeutet etwas in C ++. Wenn Sie eine C-Bibliotheksfunktion mit restrictParametern aus einem C ++ - Programm aufrufen , müssen Sie deren Auswirkungen berücksichtigen. Wenn restrictes in einer C-Bibliotheks-API verwendet wird, bedeutet dies für jeden, der es aus einer beliebigen Sprache aufruft, einschließlich des dynamischen FFI von Lisp.
Kaz

22

Nichts. Es wurde dem C99-Standard hinzugefügt.


8
Das stimmt nicht ganz. Anscheinend wird es von einigen C ++ - Compilern unterstützt und einige Leute empfehlen dringend, es zu verwenden, wenn es verfügbar ist, siehe meine Antwort unten.
Robert S. Barnes

18
@ Robert S Barnes: Der C ++ - Standard erkennt kein restrictSchlüsselwort. Daher steht meine Antwort richtig. Was Sie beschreiben, ist implementierungsspezifisches Verhalten und etwas, auf das Sie sich nicht wirklich verlassen sollten.
Dirkgently

26
@dirkgently: Bei allem Respekt, warum nicht? Viele Projekte sind an bestimmte nicht standardmäßige Spracherweiterungen gebunden, die nur von bestimmten oder sehr wenigen Compilern unterstützt werden. Der Linux-Kernel und gcc kommen mir in den Sinn. Es ist nicht ungewöhnlich, während der gesamten Nutzungsdauer eines Projekts bei einem bestimmten Compiler oder sogar einer bestimmten Revision eines bestimmten Compilers zu bleiben. Nicht jedes Programm muss streng konform sein.
Robert S. Barnes

7
@Rpbert S. Barnes: Ich kann unmöglich weiter betonen, warum Sie sich nicht auf ein implementierungsspezifisches Verhalten verlassen sollten. Was Linux und gcc betrifft, denken Sie nach und Sie werden sehen, warum sie kein gutes Beispiel für Ihre Verteidigung sind. Ich sehe noch nicht einmal eine mäßig erfolgreiche Software, die ein Leben lang auf einer einzelnen Compilerversion ausgeführt wird.
Dirkgently

16
@Rpbert S. Barnes: Die Frage lautete c ++. Nicht MSVC, nicht gcc, nicht AIX. Wenn acidzombie24 compilerspezifische Erweiterungen wollte, hätte er dies sagen / markieren sollen.
KitsuneYMG

12

Dies ist der ursprüngliche Vorschlag, dieses Schlüsselwort hinzuzufügen. Wie direkt gesagt, handelt es sich hierbei um eine C99- Funktion. es hat nichts mit C ++ zu tun.


5
Viele C ++ - Compiler unterstützen das __restrict__Schlüsselwort, das meines Erachtens identisch ist.
Robert S. Barnes

Es hat alles mit C ++ zu tun, da C ++ - Programme C-Bibliotheken aufrufen und C-Bibliotheken verwenden restrict. Das Verhalten des C ++ - Programms wird undefiniert, wenn es die durch implizierten Einschränkungen verletzt restrict.
Kaz

@kaz Völlig falsch. Es hat nichts mit C ++ zu tun, da es kein Schlüsselwort oder eine Funktion von C ++ ist. Wenn Sie C-Header-Dateien in C ++ verwenden, müssen Sie das restrictSchlüsselwort entfernen . Wenn Sie Alias-Zeiger an eine C-Funktion übergeben, die sie als eingeschränkt deklariert (was Sie entweder mit C ++ oder C tun können), ist dies natürlich undefiniert, aber das liegt an Ihnen.
Jim Balter

@ JimBalter Ich verstehe, Sie sagen also, dass C ++ - Programme C-Bibliotheken aufrufen und C-Bibliotheken verwenden restrict. Das Verhalten des C ++ - Programms wird undefiniert, wenn es die durch Einschränken implizierten Einschränkungen verletzt. Aber das hat eigentlich nichts mit C ++ zu tun, weil es "auf dir" liegt.
Kaz

5

In C ++ gibt es kein solches Schlüsselwort. Eine Liste der C ++ - Schlüsselwörter finden Sie in Abschnitt 2.11 / 1 des C ++ - Sprachstandards. restrictist ein Schlüsselwort in der C99-Version der C-Sprache und nicht in C ++.


5
Viele C ++ - Compiler unterstützen das __restrict__Schlüsselwort, das meines Erachtens identisch ist.
Robert S. Barnes

17
@ Robert: Aber es gibt kein solches Schlüsselwort in C ++ . Was einzelne Compiler tun, ist ihr eigenes Geschäft, aber es ist nicht Teil der C ++ - Sprache.
Jalf

4

Da Header-Dateien aus einigen C-Bibliotheken das Schlüsselwort verwenden, muss die C ++ - Sprache zumindest etwas dagegen tun. Dabei wird das Schlüsselwort ignoriert, sodass das Schlüsselwort nicht in einem leeren Makro definiert werden muss, um das Schlüsselwort zu unterdrücken .


3
Ich würde vermuten, dass dies entweder mithilfe einer extern CDeklaration oder durch stillschweigendes Löschen behandelt wird, wie dies beim AIX C / C ++ - Compiler der Fall ist, der stattdessen das __rerstrict__Schlüsselwort verarbeitet. Dieses Schlüsselwort wird auch unter gcc unterstützt, sodass der Code unter g ++ dasselbe kompiliert.
Robert S. Barnes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.