gcc-10.0.1 Spezifischer Segfault

23

Ich habe ein R-Paket mit C-kompiliertem Code, das seit einiger Zeit relativ stabil ist und häufig gegen eine Vielzahl von Plattformen und Compilern (Windows / OSX / Debian / Fedora GCC / Clang) getestet wird.

In jüngerer Zeit wurde eine neue Plattform hinzugefügt, um das Paket erneut zu testen:

Logs from checks with gcc trunk aka 10.0.1 compiled from source
on Fedora 30. (For some archived packages, 10.0.0.)

x86_64 Fedora 30 Linux

FFLAGS="-g -O2 -mtune=native -Wall -fallow-argument-mismatch"
CFLAGS="-g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"
CXXFLAGS="-g -O2 -Wall -pedantic -mtune=native -Wno-ignored-attributes -Wno-deprecated-declarations -Wno-parentheses -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"

Zu diesem Zeitpunkt begann der kompilierte Code sofort mit dem Segfaulting in dieser Richtung:

 *** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'

Ich konnte den Segfault konsistent reproduzieren, indem ich den rocker/r-baseDocker-Container gcc-10.0.1mit Optimierungsstufe verwendete -O2. Durch Ausführen einer niedrigeren Optimierung wird das Problem behoben. Das Ausführen eines anderen Setups, einschließlich UBSAN (gcc / clang) unter valgrind (sowohl -O0 als auch -O2), zeigt überhaupt keine Probleme. Ich bin mir auch ziemlich sicher, dass dies unterging gcc-10.0.0, aber ich habe keine Daten.

Ich habe die gcc-10.0.1 -O2Version mit ausgeführt gdbund etwas bemerkt, das mir seltsam erscheint:

GDB vs Code

Beim Durchlaufen des hervorgehobenen Abschnitts wird anscheinend die Initialisierung der zweiten Elemente der Arrays übersprungen ( R_allocist ein Wrapper um mallocden Selbstmüll, der bei der Rückgabe der Steuerung an R gesammelt wird; der Segfault tritt vor der Rückkehr zu R auf). Später stürzt das Programm ab, wenn auf das nicht initialisierte Element (in der Version gcc.10.0.1 -O2) zugegriffen wird.

Ich habe dies behoben, indem ich das betreffende Element überall im Code explizit initialisiert habe, was schließlich zur Verwendung des Elements führte, aber es hätte wirklich mit einer leeren Zeichenfolge initialisiert werden müssen, oder zumindest hätte ich das angenommen.

Vermisse ich etwas Offensichtliches oder mache ich etwas Dummes? Beides ist ziemlich wahrscheinlich, da C bei weitem meine zweite Sprache ist . Es ist nur seltsam, dass dies gerade aufgetaucht ist, und ich kann nicht herausfinden, was der Compiler versucht zu tun.


UPDATE : Anweisungen , dies zu reproduzieren, obwohl dies nur so lange reproduzieren als debian:testingDocker Behälter gcc-10an gcc-10.0.1. Auch nicht nur diese Befehle ausführen , wenn Sie mir nicht trauen .

Dies ist leider kein minimal reproduzierbares Beispiel.

docker pull rocker/r-base
docker run --rm -ti --security-opt seccomp=unconfined \
  rocker/r-base /bin/bash
apt-get update
apt-get install gcc-10 gdb
gcc-10 --version  # confirm 10.0.1
# gcc-10 (Debian 10-20200222-1) 10.0.1 20200222 (experimental) 
# [master revision 01af7e0a0c2:487fe13f218:e99b18cf7101f205bfdd9f0f29ed51caaec52779]

mkdir ~/.R
touch ~/.R/Makevars
echo "CC = gcc-10
CFLAGS = -g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection
" >> ~/.R/Makevars

R -d gdb --vanilla

Dann in der R-Konsole, nachdem Sie eingegeben haben run, um gdbdas Programm auszuführen:

f.dl <- tempfile()
f.uz <- tempfile()

github.url <- 'https://github.com/brodieG/vetr/archive/v0.2.8.zip'

download.file(github.url, f.dl)
unzip(f.dl, exdir=f.uz)
install.packages(
  file.path(f.uz, 'vetr-0.2.8'), repos=NULL,
  INSTALL_opts="--install-tests", type='source'
)
# minimal set of commands to segfault
library(vetr)
alike(pairlist(a=1, b="character"), pairlist(a=1, b=letters))
alike(pairlist(1, "character"), pairlist(1, letters))
alike(NULL, 1:3)                  # not a wild card at top level
alike(list(NULL), list(1:3))      # but yes when nested
alike(list(NULL, NULL), list(list(list(1, 2, 3)), 1:25))
alike(list(NULL), list(1, 2))
alike(list(), list(1, 2))
alike(matrix(integer(), ncol=7), matrix(1:21, nrow=3))
alike(matrix(character(), nrow=3), matrix(1:21, nrow=3))
alike(
  matrix(integer(), ncol=3, dimnames=list(NULL, c("R", "G", "B"))),
  matrix(1:21, ncol=3, dimnames=list(NULL, c("R", "G", "B")))
)

# Adding tests from docs

mx.tpl <- matrix(
  integer(), ncol=3, dimnames=list(row.id=NULL, c("R", "G", "B"))
)
mx.cur <- matrix(
  sample(0:255, 12), ncol=3, dimnames=list(row.id=1:4, rgb=c("R", "G", "B"))
)
mx.cur2 <-
  matrix(sample(0:255, 12), ncol=3, dimnames=list(1:4, c("R", "G", "B")))

alike(mx.tpl, mx.cur2)

Die Überprüfung in gdb zeigt ziemlich schnell (wenn ich das richtig verstehe), dass CSR_strmlen_xversucht wird, auf die Zeichenfolge zuzugreifen, die nicht initialisiert wurde.

UPDATE 2 : Dies ist eine sehr rekursive Funktion, und außerdem wird das String-Initialisierungsbit viele, viele Male aufgerufen. Dies ist meistens b / c. Ich war faul. Wir müssen die Zeichenfolgen nur einmal initialisieren, wenn wir tatsächlich auf etwas stoßen, das wir in der Rekursion melden möchten. Es war jedoch einfacher, jedes Mal zu initialisieren, wenn es möglich ist, auf etwas zu stoßen. Ich erwähne dies, weil das, was Sie als nächstes sehen werden, mehrere Initialisierungen zeigt, aber nur eine davon (vermutlich die mit der Adresse <0x1400000001>) verwendet wird.

Ich kann nicht garantieren, dass das hier gezeigte Material direkt mit dem Element zusammenhängt, das den Segfault verursacht hat (obwohl es sich um denselben illegalen Adresszugriff handelt), aber wie @ nate-eldredge gefragt hat, zeigt es, dass das Array-Element nicht vorhanden ist entweder kurz vor der Rückkehr oder kurz nach der Rückkehr in der aufrufenden Funktion initialisiert. Beachten Sie, dass die aufrufende Funktion 8 davon initialisiert, und ich zeige sie alle, wobei alle entweder mit Müll oder unzugänglichem Speicher gefüllt sind.

Geben Sie hier die Bildbeschreibung ein

UPDATE 3 , Demontage der betreffenden Funktion:

Breakpoint 1, ALIKEC_res_strings_init () at alike.c:75
75    return res;
(gdb) p res.current[0]
$1 = 0x7ffff46a0aa5 "%s%s%s%s"
(gdb) p res.current[1]
$2 = 0x1400000001 <error: Cannot access memory at address 0x1400000001>
(gdb) disas /m ALIKEC_res_strings_init
Dump of assembler code for function ALIKEC_res_strings_init:
53  struct ALIKEC_res_strings ALIKEC_res_strings_init() {
   0x00007ffff4687fc0 <+0>: endbr64 

54    struct ALIKEC_res_strings res;

55  
56    res.target = (const char **) R_alloc(5, sizeof(const char *));
   0x00007ffff4687fc4 <+4>: push   %r12
   0x00007ffff4687fc6 <+6>: mov    $0x8,%esi
   0x00007ffff4687fcb <+11>:    mov    %rdi,%r12
   0x00007ffff4687fce <+14>:    push   %rbx
   0x00007ffff4687fcf <+15>:    mov    $0x5,%edi
   0x00007ffff4687fd4 <+20>:    sub    $0x8,%rsp
   0x00007ffff4687fd8 <+24>:    callq  0x7ffff4687180 <R_alloc@plt>
   0x00007ffff4687fdd <+29>:    mov    $0x8,%esi
   0x00007ffff4687fe2 <+34>:    mov    $0x5,%edi
   0x00007ffff4687fe7 <+39>:    mov    %rax,%rbx

57    res.current = (const char **) R_alloc(5, sizeof(const char *));
   0x00007ffff4687fea <+42>:    callq  0x7ffff4687180 <R_alloc@plt>

58  
59    res.target[0] = "%s%s%s%s";
   0x00007ffff4687fef <+47>:    lea    0x1764a(%rip),%rdx        # 0x7ffff469f640
   0x00007ffff4687ff6 <+54>:    lea    0x18aa8(%rip),%rcx        # 0x7ffff46a0aa5
   0x00007ffff4687ffd <+61>:    mov    %rcx,(%rbx)

60    res.target[1] = "";

61    res.target[2] = "";
   0x00007ffff4688000 <+64>:    mov    %rdx,0x10(%rbx)

62    res.target[3] = "";
   0x00007ffff4688004 <+68>:    mov    %rdx,0x18(%rbx)

63    res.target[4] = "";
   0x00007ffff4688008 <+72>:    mov    %rdx,0x20(%rbx)

64  
65    res.tar_pre = "be";

66  
67    res.current[0] = "%s%s%s%s";
   0x00007ffff468800c <+76>:    mov    %rax,0x8(%r12)
   0x00007ffff4688011 <+81>:    mov    %rcx,(%rax)

68    res.current[1] = "";

69    res.current[2] = "";
   0x00007ffff4688014 <+84>:    mov    %rdx,0x10(%rax)

70    res.current[3] = "";
   0x00007ffff4688018 <+88>:    mov    %rdx,0x18(%rax)

71    res.current[4] = "";
   0x00007ffff468801c <+92>:    mov    %rdx,0x20(%rax)

72  
73    res.cur_pre = "is";

74  
75    return res;
=> 0x00007ffff4688020 <+96>:    lea    0x14fe0(%rip),%rax        # 0x7ffff469d007
   0x00007ffff4688027 <+103>:   mov    %rax,0x10(%r12)
   0x00007ffff468802c <+108>:   lea    0x14fcd(%rip),%rax        # 0x7ffff469d000
   0x00007ffff4688033 <+115>:   mov    %rbx,(%r12)
   0x00007ffff4688037 <+119>:   mov    %rax,0x18(%r12)
   0x00007ffff468803c <+124>:   add    $0x8,%rsp
   0x00007ffff4688040 <+128>:   pop    %rbx
   0x00007ffff4688041 <+129>:   mov    %r12,%rax
   0x00007ffff4688044 <+132>:   pop    %r12
   0x00007ffff4688046 <+134>:   retq   
   0x00007ffff4688047:  nopw   0x0(%rax,%rax,1)

End of assembler dump.

UPDATE 4 :

Der Versuch, den Standard hier zu analysieren, scheint also relevant zu sein ( C11-Entwurf ):

6.3.2.3 Par7-Konvertierungen> Andere Operanden> Zeiger

Ein Zeiger auf einen Objekttyp kann in einen Zeiger auf einen anderen Objekttyp konvertiert werden. Wenn der resultierende Zeiger für den referenzierten Typ nicht korrekt ausgerichtet ist 68), ist das Verhalten undefiniert.
Andernfalls wird das Ergebnis bei erneuter Konvertierung gleich dem ursprünglichen Zeiger verglichen. Wenn ein Zeiger auf ein Objekt in einen Zeiger auf einen Zeichentyp konvertiert wird, zeigt das Ergebnis auf das niedrigste adressierte Byte des Objekts. Aufeinanderfolgende Inkremente des Ergebnisses bis zur Größe des Objekts ergeben Zeiger auf die verbleibenden Bytes des Objekts.

6.5 Par6-Ausdrücke

Der effektive Typ eines Objekts für den Zugriff auf seinen gespeicherten Wert ist der deklarierte Typ des Objekts, falls vorhanden. 87) Wenn ein Wert in einem Objekt ohne deklarierten Typ über einen Wert mit einem Typ gespeichert wird, der kein Zeichentyp ist, wird der Typ des Werts zum effektiven Typ des Objekts für diesen Zugriff und für nachfolgende Zugriffe, die dies nicht tun Ändern Sie den gespeicherten Wert. Wenn ein Wert mit memcpy oder memmove in ein Objekt ohne deklarierten Typ kopiert oder als Array mit Zeichentyp kopiert wird, ist der effektive Typ des geänderten Objekts für diesen Zugriff und für nachfolgende Zugriffe, die den Wert nicht ändern, der effektiver Typ des Objekts, von dem der Wert kopiert wird, falls vorhanden. Bei allen anderen Zugriffen auf ein Objekt ohne deklarierten Typ ist der effektive Typ des Objekts einfach der Typ des für den Zugriff verwendeten l-Werts.

87) Zugeordnete Objekte haben keinen deklarierten Typ.

IIUC R_allocgibt einen Versatz in einen malloced-Block zurück, dessen doubleAusrichtung garantiert ist , und die Größe des Blocks nach dem Versatz entspricht der angeforderten Größe (es gibt auch eine Zuordnung vor dem Versatz für R-spezifische Daten). R_allocWirkt diesen Zeiger (char *)bei der Rückkehr.

Abschnitt 6.2.5 Abs. 29

Ein Zeiger auf void muss die gleichen Darstellungs- und Ausrichtungsanforderungen haben wie ein Zeiger auf einen Zeichentyp. 48) Ebenso müssen Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen dieselben Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Strukturtypen müssen die gleichen Darstellungs- und Ausrichtungsanforderungen haben.
Alle Zeiger auf Vereinigungstypen müssen dieselben Darstellungs- und Ausrichtungsanforderungen haben.
Zeiger auf andere Typen müssen nicht dieselben Darstellungs- oder Ausrichtungsanforderungen haben.

48) Dieselben Darstellungs- und Ausrichtungsanforderungen sollen Austauschbarkeit als Argumente für Funktionen, Rückgabewerte von Funktionen und Gewerkschaftsmitglieder implizieren.

Die Frage ist also „wir sind die neu zu fassen erlaubt (char *)zu (const char **)und schreiben , um es als (const char **)“. Ich habe oben gelesen, dass es in doubleOrdnung ist, solange Zeiger auf den Systemen, auf denen der Code ausgeführt wird, mit der Ausrichtung kompatibel sind .

Verstoßen wir gegen "striktes Aliasing"? dh:

6.5 Abs. 7

Auf einen gespeicherten Wert eines Objekts darf nur über einen lvalue-Ausdruck zugegriffen werden, der einen der folgenden Typen hat: 88)

- ein Typ, der mit dem effektiven Typ des Objekts kompatibel ist ...

88) Mit dieser Liste sollen die Umstände angegeben werden, unter denen ein Objekt möglicherweise einen Alias ​​aufweist oder nicht.

Was sollte der Compiler also für den effektiven Typ des Objekts halten, auf das res.target(oder res.current) zeigt? Vermutlich der deklarierte Typ (const char **), oder ist dieser tatsächlich mehrdeutig? Ich habe das Gefühl, dass dies in diesem Fall nicht nur deshalb der Fall ist, weil es keinen anderen "Wert" im Bereich gibt, der auf dasselbe Objekt zugreift.

Ich gebe zu, ich habe große Mühe, diesen Abschnitten des Standards Sinn zu entziehen.

BrodieG
quelle
Wenn nicht bereits geprüft, kann es sinnvoll sein, die Demontage zu betrachten, um genau zu sehen, was getan wird. Und auch um die Demontage zwischen gcc-Versionen zu vergleichen.
Kaylum
2
Ich würde nicht versuchen, mich mit der Trunk-Version von GCC anzulegen. Es ist schön, Spaß damit zu haben, aber es heißt aus einem bestimmten Grund Kofferraum. Leider ist es fast unmöglich zu sagen, was falsch ist, ohne (1) Ihren Code und die genaue Konfiguration (2) dieselbe GCC-Version (3) auf derselben Architektur zu haben. Ich würde vorschlagen, zu überprüfen, ob dies weiterhin besteht, wenn 10.0.1 vom Trunk in den Stall wechselt.
Marco Bonelli
1
Noch ein Kommentar: -mtune=nativeOptimiert für die jeweilige CPU Ihres Computers. Dies ist für verschiedene Tester unterschiedlich und kann Teil des Problems sein. Wenn Sie die Kompilierung mit ausführen -v, sollten Sie sehen können, welche CPU-Familie sich auf Ihrem Computer befindet (z. B. -mtune=skylakeauf meinem Computer).
Nate Eldredge
1
An Debug-Läufen immer noch schwer zu erkennen. Die Demontage sollte abschließend sein. Sie müssen nichts extrahieren, sondern nur die beim Kompilieren des Projekts erzeugte O-Datei suchen und zerlegen. Sie können auch die disassembleAnweisung in gdb verwenden.
Nate Eldredge
5
Wie auch immer, herzlichen Glückwunsch, Sie sind einer der wenigen, deren Problem tatsächlich ein Compiler-Fehler war.
Nate Eldredge

Antworten:

22

Zusammenfassung: Dies scheint ein Fehler in gcc zu sein, der mit der Zeichenfolgenoptimierung zusammenhängt. Ein in sich geschlossener Testfall ist unten. Anfangs gab es einige Zweifel, ob der Code korrekt ist, aber ich denke, das ist es.

Ich habe den Fehler als PR 93982 gemeldet . Ein vorgeschlagener Fix wurde festgeschrieben , aber nicht in allen Fällen, was zum Follow-up PR 94015 ( Godbolt-Link ) führte.

Sie sollten in der Lage sein, den Fehler zu umgehen, indem Sie mit dem Flag kompilieren -fno-optimize-strlen.


Ich konnte Ihren Testfall auf das folgende minimale Beispiel reduzieren (auch auf Godbolt ):

struct a {
    const char ** target;
};

char* R_alloc(void);

struct a foo(void) {
    struct a res;
    res.target = (const char **) R_alloc();
    res.target[0] = "12345678";
    res.target[1] = "";
    res.target[2] = "";
    res.target[3] = "";
    res.target[4] = "";
    return res;
}

Mit gcc trunk (gcc version 10.0.1 20200225 (experimentell)) und -O2(alle anderen Optionen erwiesen sich als unnötig) lautet die generierte Assembly auf amd64 wie folgt:

.LC0:
        .string "12345678"
.LC1:
        .string ""
foo:
        subq    $8, %rsp
        call    R_alloc
        movq    $.LC0, (%rax)
        movq    $.LC1, 16(%rax)
        movq    $.LC1, 24(%rax)
        movq    $.LC1, 32(%rax)
        addq    $8, %rsp
        ret

Sie haben also völlig Recht, dass der Compiler nicht initialisiert werden kann res.target[1](beachten Sie das auffällige Fehlen von movq $.LC1, 8(%rax)).

Es ist interessant, mit dem Code zu spielen und zu sehen, was den "Bug" betrifft. Wenn Sie den Rückgabetyp von R_allocauf ändern , void *wird er möglicherweise nicht mehr angezeigt, und Sie erhalten eine "korrekte" Baugruppenausgabe. Vielleicht weniger bedeutend, aber amüsanter: Wenn Sie die Saite "12345678"entweder länger oder kürzer ändern, verschwindet sie auch.


Vorherige Diskussion, jetzt gelöst - der Code ist anscheinend legal.

Die Frage, die ich habe, ist, ob Ihr Code tatsächlich legal ist. Die Tatsache , dass Sie das nehmen char *von zurück R_alloc()und warf es auf const char **, und speichern Sie dann eine const char *scheint , wie es die verletzen könnten strenge Aliasing - Regel , wie charund const char *nicht kompatible Typen. Es gibt eine Ausnahme, mit der Sie auf jedes Objekt als zugreifen können char(um Dinge wie memcpy) zu implementieren , aber dies ist umgekehrt, und so gut ich es verstehe, ist dies nicht zulässig. Dadurch erzeugt Ihr Code ein undefiniertes Verhalten, und der Compiler kann legal alles tun, was er will.

Ist dies der Fall, wäre die richtige fix sein für R , ihren Code zu ändern , so dass R_alloc()Renditen void *statt char *. Dann würde es kein Aliasing-Problem geben. Leider liegt dieser Code außerhalb Ihrer Kontrolle, und mir ist nicht klar, wie Sie diese Funktion überhaupt verwenden können, ohne das strikte Aliasing zu verletzen. Eine Problemumgehung könnte darin bestehen, eine temporäre Variable einzufügen, z. B. void *tmp = R_alloc(); res.target = tmp;die das Problem im Testfall löst, aber ich bin mir immer noch nicht sicher, ob es legal ist.

Ich bin mir jedoch dieser "strengen Aliasing" -Hypothese nicht sicher, da das Kompilieren mit -fno-strict-aliasing, mit dem AFAIK gcc solche Konstrukte zulassen soll , das Problem nicht beseitigt!


Aktualisieren. Beim Ausprobieren verschiedener Optionen stellte ich fest, dass entweder "korrekter" Code generiert wird -fno-optimize-strlenoder -fno-tree-forwpropwird. Außerdem -O1 -foptimize-strlenergibt die Verwendung den falschen Code (aber -O1 -ftree-forwpropnicht).

Nach einer kleinen git bisectÜbung scheint der Fehler in Commit 34fcf41e30ff56155e996f5e04 eingeführt worden zu sein .


Update 2. Ich habe versucht, ein wenig in die gcc-Quelle zu graben, um zu sehen, was ich lernen kann. (Ich behaupte nicht, irgendein Compiler-Experte zu sein!)

Es sieht so aus, als ob der Code in tree-ssa-strlen.cdazu gedacht ist, die im Programm angezeigten Zeichenfolgen zu verfolgen. Soweit ich das beurteilen kann, besteht der Fehler darin, dass res.target[0] = "12345678";der Compiler beim Betrachten der Anweisung die Adresse des Zeichenfolgenliteral "12345678"mit der Zeichenfolge selbst verknüpft. ( Dies scheint mit diesem verdächtigen Code zu tun zu haben, der im oben genannten Commit hinzugefügt wurde. Wenn versucht wird, die Bytes einer "Zeichenfolge" zu zählen, die tatsächlich eine Adresse ist, wird stattdessen geprüft, auf was diese Adresse verweist.)

Es wird also angenommen, dass die Anweisung res.target[0] = "12345678", anstatt die Adresse von "12345678"an der Adresse zu res.targetspeichern, die Zeichenfolge selbst an dieser Adresse speichert, als ob die Anweisung wäre strcpy(res.target, "12345678"). Beachten Sie für die Zukunft, dass dies dazu führen würde, dass die nachfolgende Null an der Adresse gespeichert wird res.target+8(zu diesem Zeitpunkt im Compiler sind alle Offsets in Bytes).

Wenn der Compiler nun betrachtet res.target[1] = "", behandelt er dies ebenfalls so, als wäre es strcpy(res.target+8, "")die 8, die von der Größe von a kommt char *. Das heißt, als würde einfach ein Nullbyte an der Adresse gespeichert res.target+8. Der Compiler "weiß" jedoch, dass die vorherige Anweisung bereits ein Null-Byte an genau dieser Adresse gespeichert hat! Daher ist diese Anweisung "redundant" und kann ( hier ) verworfen werden .

Dies erklärt, warum die Zeichenfolge genau 8 Zeichen lang sein muss, um den Fehler auszulösen. (Obwohl andere Vielfache von 8 den Fehler auch in anderen Situationen auslösen können.)

Nate Eldredge
quelle
Die Neufassung von FWIW auf einen anderen Zeigertyp ist dokumentiert . Ich weiß nichts über Aliasing, um zu wissen, ob es in Ordnung ist, eine Neufassung vorzunehmen, int*aber nicht const char**.
BrodieG
Wenn mein Verständnis von striktem Aliasing korrekt ist, ist die Besetzung int *auch illegal (oder besser gesagt, das Speichern von ints dort ist illegal).
Nate Eldredge
1
Dies hat nichts mit einer strengen Aliasing-Regel zu tun. Bei einer strengen Aliasing-Regel wird auf die Daten zugegriffen, die Sie bereits mit einem anderen Handle gespeichert haben . Da Sie hier nur zuweisen, wird die strenge Aliasing-Regel nicht berührt. Das Casting von Zeigern ist gültig, wenn beide Zeigertypen die gleichen Ausrichtungsanforderungen haben, aber hier wird von char*x86_64 gegossen und daran gearbeitet ... Ich sehe hier keine UB, dies ist ein gcc-Fehler.
KamilCuk
1
Ja und nein, @KamilCuk. In der Terminologie des Standards umfasst "Zugriff" sowohl das Lesen als auch das Ändern des Werts eines Objekts. Die strenge Aliasing-Regel spricht daher von "Speichern". Es ist nicht auf Rücklesevorgänge beschränkt. Bei Objekten ohne deklarierten Typ ist dies jedoch darauf zurückzuführen, dass beim Schreiben in ein solches Objekt der effektive Typ automatisch so geändert wird , dass er dem entspricht, was geschrieben wurde. Die Objekte ohne deklarierten Typ sind genau die dynamisch zugewiesenen Objekte (unabhängig vom Typ des Zeigers, über den auf sie zugegriffen wird), sodass hier tatsächlich keine SA-Verletzung vorliegt.
John Bollinger
2
Ja, @Nate, mit dieser Definition von R_alloc()ist das Programm konform, unabhängig davon, in welcher Übersetzungseinheit R_alloc()definiert ist. Es ist der Compiler, der hier nicht konform ist.
John Bollinger