Hilft Beschränkung in C, wenn ein Zeiger bereits als const gekennzeichnet ist?

Lesezeit: 11 Minuten

Benutzer-Avatar
Anteru

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 constkö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.

  • 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 heben const float *__restrict aaber gcc nicht.

    – Peter Cordes

    28. April 2017 um 2:48 Uhr

  • Du hast die Flagge C und C++ aber const in C und const 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 meint read onlydie Hardware und andere Prozesse können es möglicherweise noch ändern (Beispiel ist das Ergebnisregister eines ADC, das wäre const und volatile). 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


Benutzer-Avatar
falstro

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.

  • Tatsächlich sind die Zeiger von Anteru nicht konstant, sondern die von den Zeigern adressierten Daten sind konstant.

    – Todd Lehmann

    11. August 2014 um 4:09 Uhr


  • @ToddLehman Die von den Zeigern adressierten Daten sind möglicherweise nicht konstant und können tatsächlich während der Ausführung der Funktion geändert werden. Das const bedeutet nur, dass die Funktion diese Variablen nicht ohne Umwandlung durch diese Zeiger ändern kann.

    – MM

    28. April 2017 um 2:49 Uhr

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).

  • Das ist keine Antwort.

    – Trevor Hickey

    5. Dezember 2015 um 18:13 Uhr

  • ‘const float* a’ definiert die konstante Adresse (Zeigerwert) des Zeigers, sagt aber noch nichts über die Daten aus, auf die gezeigt wird. Es hängt davon ab, wie Sie die Definitionen konstanter Zeiger und Speicher verwenden, aber zu sagen, dass es sich nicht um einen konstanten Zeiger, sondern um einen konstanten Speicher handelt, ist wirklich mehrdeutig und kann jemanden sogar noch mehr verwirren.

    – judoka_acl

    20. Juli 2016 um 9:55 Uhr

  • @lalamer: Ich denke “Zeiger auf const” ist ein guter Begriff für Hinweise wie const float *p. Natürlich werden die Leute immer noch “const pointer” sagen und denken, weil es kürzer ist (und allgemein verwendet wird). Aber wenn Sie jemals genau sein müssen, ist dies die klarste Terminologie. Sie können sogar leicht über einen const-Zeiger auf const sprechen (const float *const fp).

    – Peter Cordes

    28. April 2017 um 1:35 Uhr

  • Das ist genau die Frage, nach der ich gesucht habe, und die aktuelle Frage hilft überhaupt nicht

    – Abdurrahim

    17. Oktober 2019 um 19:25 Uhr

Benutzer-Avatar
Peter Kordes

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 restrictmit 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 constness 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 restricttut 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 resultsowohl 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

  • Sie können es sogar durch diesen Zeiger ändern, indem Sie es wegwerfen const ; solange das Objekt, auf das gezeigt wird, kein konstantes Objekt ist

    – MM

    28. April 2017 um 2:50 Uhr

  • @MM: Ich würde argumentieren, dass das Ergebnis einer Umwandlung ein neuer (unbenannter temporärer) Zeiger ist. Deshalb habe ich die Formulierung verwendet, die ich gemacht habe: Sie kann es nicht ändern das Zeiger. Aber ja, ich stimme deinem Punkt zu. Ich denke, der Hauptpunkt von const und Zeiger-auf-const ist eine verbesserte Prüfung zur Kompilierzeit. Es ist nicht einmal ein Versprechen an den Compiler, dass eine Funktion einen Zeiger auf eine Konstante nicht zurück in einen Zeiger auf eine Nichtkonstante umwandelt. z.B nach Anruf external_function(const int*)gcc und clang laden die von diesem Zeiger geladenen Daten neu..

    – Peter Cordes

    28. April 2017 um 4:19 Uhr


  • @MM: Die Antwort wurde mit mehr Informationen über die Wirkung von aktualisiert restrict auf Nur-Lese-Zeiger. Ich habe einen Fall gefunden, wo restrict lässt clang (aber nicht gcc) ein Neuladen nach einer nicht-inline-Funktion vermeiden.

    – Peter Cordes

    9. Mai 2017 um 7:02 Uhr


Benutzer-Avatar
Vincent J. Lipsio

In dem C-99-Standard (ISO/IEC 9899:1999 (E)) es gibt beispiele dafür const * restrictzB 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 * restrictdann 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.

  • Tatsächlich geht der Compiler davon aus, dass nichts außerhalb der Funktion den nicht deklarierten Speicher ändert volatile. (Außer natürlich eine andere Funktion, die von dieser Funktion aufgerufen wird).

    – sup

    4. November 2012 um 20:58 Uhr


  • Tatsächlich geht der Compiler davon aus, dass nichts außerhalb der Funktion den nicht deklarierten Speicher ändert volatile. (Außer natürlich eine andere Funktion, die von dieser Funktion aufgerufen wird).

    – sup

    4. November 2012 um 20:58 Uhr


1351460cookie-checkHilft Beschränkung in C, wenn ein Zeiger bereits als const gekennzeichnet ist?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy