gcc-10.0.1 Segfault Khusus

23

Saya punya paket R dengan kode kompilasi C yang sudah relatif stabil untuk sementara waktu dan sering diuji terhadap berbagai platform dan kompiler (windows / osx / debian / fedora gcc / clang).

Baru-baru ini platform baru ditambahkan untuk menguji paket lagi:

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"

Pada titik mana kode yang dikompilasi segera mulai melakukan segmentasi di sepanjang baris berikut:

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

Saya dapat mereproduksi segfault secara konsisten dengan menggunakan rocker/r-basewadah buruh pelabuhan gcc-10.0.1dengan tingkat pengoptimalan -O2. Menjalankan optimasi yang lebih rendah menghilangkan masalah. Menjalankan set-up lainnya, termasuk di bawah valgrind (keduanya -O0 dan -O2), UBSAN (gcc / clang), tidak menunjukkan masalah sama sekali. Saya juga cukup yakin ini berjalan di bawah gcc-10.0.0, tetapi tidak memiliki data.

Saya menjalankan gcc-10.0.1 -O2versi itu gdbdan memperhatikan sesuatu yang terasa aneh bagi saya:

gdb vs code

Sementara melangkah melalui bagian yang disorot tampak inisialisasi elemen kedua array dilewati ( R_allocadalah pembungkus di sekitar mallocpengumpulan sampah sendiri ketika mengembalikan kontrol ke R; segfault terjadi sebelum kembali ke R). Kemudian, program macet ketika elemen yang tidak diinisialisasi (dalam versi gcc.10.0.1 -O2) diakses.

Saya memperbaikinya dengan menginisialisasi elemen secara eksplisit di mana-mana dalam kode yang akhirnya mengarah ke penggunaan elemen, tetapi itu seharusnya benar-benar diinisialisasi ke string kosong, atau setidaknya itulah yang akan saya asumsikan.

Apakah saya kehilangan sesuatu yang jelas atau melakukan sesuatu yang bodoh? Keduanya cukup masuk akal karena C adalah bahasa kedua saya sejauh ini . Hanya aneh bahwa ini baru saja dipotong sekarang, dan saya tidak tahu apa yang coba dilakukan oleh kompiler.


UPDATE : Petunjuk untuk mereproduksi ini, meskipun ini hanya akan mereproduksi selama debian:testingwadah buruh pelabuhan memiliki gcc-10di gcc-10.0.1. Juga, jangan hanya menjalankan perintah ini jika Anda tidak percaya padaku .

Maaf ini bukan contoh minimal yang dapat direproduksi.

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

Kemudian di konsol R, setelah mengetik rununtuk mendapatkan gdbuntuk menjalankan program:

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)

Memeriksa di gdb menunjukkan cukup cepat (jika saya mengerti benar) yang CSR_strmlen_xmencoba mengakses string yang tidak diinisialisasi.

UPDATE 2 : ini adalah fungsi yang sangat rekursif, dan di atas itu bit inisialisasi string dipanggil berkali-kali. Ini sebagian besar b / c saya sedang malas, kita hanya perlu string diinisialisasi untuk satu kali kita benar-benar menemukan sesuatu yang ingin kita laporkan dalam rekursi, tetapi lebih mudah untuk menginisialisasi setiap kali mungkin untuk menemukan sesuatu. Saya menyebutkan ini karena apa yang akan Anda lihat selanjutnya menunjukkan beberapa inisialisasi, tetapi hanya satu dari mereka (mungkin yang dengan alamat <0x1400000001>) sedang digunakan.

Saya tidak dapat menjamin bahwa hal-hal yang saya perlihatkan di sini secara langsung berkaitan dengan elemen yang menyebabkan segfault (meskipun itu adalah akses alamat ilegal yang sama), tetapi ketika @ nate-eldredge bertanya, itu menunjukkan bahwa elemen array tidak diinisialisasi baik sebelum kembali atau tepat setelah kembali dalam fungsi panggilan. Perhatikan fungsi panggilan menginisialisasi 8 dari ini, dan saya tunjukkan semuanya, dengan semuanya dipenuhi dengan sampah atau memori yang tidak dapat diakses.

masukkan deskripsi gambar di sini

UPDATE 3 , pembongkaran fungsi yang dimaksud:

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.

PEMBARUAN 4 :

Jadi, mencoba mengurai melalui standar di sini adalah bagian-bagiannya yang tampaknya relevan ( draft C11 ):

6.3.2.3 Konversi Par7> Operan Lain> Pointer

Pointer ke tipe objek dapat dikonversi ke pointer ke tipe objek yang berbeda. Jika pointer yang dihasilkan tidak sejajar dengan benar ( 68) untuk tipe yang direferensikan, perilaku tidak terdefinisi.
Kalau tidak, ketika dikonversi kembali lagi, hasilnya harus membandingkan sama dengan pointer asli. Ketika pointer ke objek dikonversi ke pointer ke tipe karakter, hasilnya menunjuk ke byte terendah dari objek. Peningkatan hasil berturut-turut, hingga ukuran objek, menghasilkan pointer ke byte tersisa dari objek.

6.5 Par6 Ekspresi

Jenis objek yang efektif untuk akses ke nilai yang disimpannya adalah tipe objek yang dinyatakan, jika ada. 87) Jika suatu nilai disimpan ke dalam objek yang tidak memiliki tipe yang dideklarasikan melalui nilai lv yang memiliki tipe yang bukan tipe karakter, maka tipe nilai tersebut menjadi tipe efektif objek untuk akses itu dan untuk akses selanjutnya yang tidak. ubah nilai yang disimpan. Jika nilai disalin ke objek yang tidak memiliki tipe yang dideklarasikan menggunakan memcpy atau memmove, atau disalin sebagai array tipe karakter, maka tipe efektif objek yang dimodifikasi untuk akses tersebut dan untuk akses selanjutnya yang tidak mengubah nilai adalah jenis objek yang efektif dari mana nilai disalin, jika ada. Untuk semua akses lainnya ke objek yang tidak memiliki tipe yang dideklarasikan, tipe efektif objek hanyalah tipe nilai yang digunakan untuk akses.

87) Objek yang dialokasikan tidak memiliki tipe yang dideklarasikan.

IIUC R_allocmengembalikan offset ke mallocblok ed yang dijamin akan doubleselaras, dan ukuran blok setelah offset adalah dari ukuran yang diminta (ada juga alokasi sebelum offset untuk data spesifik R). R_allocmelemparkan penunjuk itu ke belakang (char *).

Bagian 6.2.5 Par 29

Penunjuk untuk membatalkan harus memiliki persyaratan representasi dan perataan yang sama seperti penunjuk ke tipe karakter. 48) Demikian pula, pointer ke versi yang memenuhi syarat atau tidak memenuhi syarat dari jenis yang kompatibel harus memiliki persyaratan representasi dan perataan yang sama. Semua pointer ke tipe struktur harus memiliki persyaratan representasi dan perataan yang sama satu sama lain.
Semua pointer ke tipe-tipe serikat pekerja harus memiliki persyaratan representasi dan penyelarasan yang sama satu sama lain.
Pointer ke tipe lain tidak perlu memiliki representasi atau persyaratan penyelarasan yang sama.

48) Persyaratan representasi dan penyelarasan yang sama dimaksudkan untuk menyiratkan argumen yang dapat dipertukarkan ke fungsi, mengembalikan nilai dari fungsi, dan anggota serikat pekerja.

Jadi pertanyaannya adalah "apakah kita diizinkan untuk menyusun kembali (char *)ke (const char **)dan menulis sebagai (const char **)". Bacaan saya di atas adalah bahwa selama pointer pada sistem kode dijalankan memiliki keselarasan yang kompatibel dengan doublepenyelarasan, maka tidak apa-apa.

Apakah kita melanggar "aliasing ketat"? yaitu:

6.5 Par 7

Objek harus memiliki nilai tersimpan diakses hanya oleh ekspresi lvalue yang memiliki salah satu dari jenis berikut: 88)

- jenis yang kompatibel dengan jenis objek yang efektif ...

88) Maksud dari daftar ini adalah untuk menentukan keadaan-keadaan di mana suatu objek mungkin atau mungkin tidak disebutkan.

Jadi, apa yang seharusnya dikompilasi oleh kompilator jenis objek yang ditunjuk oleh res.target(atau res.current)? Agaknya tipe yang dideklarasikan (const char **), atau apakah ini sebenarnya ambigu? Rasanya bagi saya bahwa tidak dalam kasus ini hanya karena tidak ada 'nilai' lain dalam ruang lingkup yang mengakses objek yang sama.

Saya akui saya berjuang keras untuk mengeluarkan akal dari bagian-bagian standar ini.

BrodieG
sumber
Jika belum diperiksa mungkin ada baiknya melihat pembongkaran untuk melihat apa yang sedang dilakukan. Dan juga untuk membandingkan pembongkaran antara versi gcc.
kaylum
2
Saya tidak akan mencoba mengacaukan versi trunk GCC. Sangat menyenangkan untuk bersenang-senang, tetapi itu disebut trunk karena suatu alasan. Sayangnya hampir tidak mungkin untuk mengatakan apa yang salah tanpa (1) memiliki kode Anda dan konfigurasi yang tepat (2) memiliki versi GCC yang sama (3) pada arsitektur yang sama. Saya sarankan memeriksa apakah ini bertahan ketika 10.0.1 bergerak dari bagasi ke stabil.
Marco Bonelli
1
Satu komentar lagi: -mtune=nativemengoptimalkan untuk CPU tertentu yang dimiliki mesin Anda. Itu akan berbeda untuk penguji yang berbeda dan mungkin menjadi bagian dari masalah. Jika Anda menjalankan kompilasi dengan -vAnda harus dapat melihat keluarga cpu mana yang ada di mesin Anda (misalnya -mtune=skylakedi komputer saya).
Nate Eldredge
1
Masih sulit untuk mengetahui dari proses debug. Pembongkaran harus konklusif. Anda tidak perlu mengekstrak apa pun, cukup cari file .o yang dihasilkan saat Anda menyusun proyek dan membongkarnya. Anda juga bisa menggunakan disassembleinstruksi di dalam gdb.
Nate Eldredge
5
Ngomong-ngomong, selamat, Anda salah satu dari sedikit orang yang masalahnya sebenarnya adalah bug penyusun.
Nate Eldredge

Jawaban:

22

Rangkuman: Ini tampaknya merupakan bug dalam gcc, terkait dengan optimasi string. Testcase mandiri ada di bawah ini. Awalnya ada beberapa keraguan apakah kode itu benar, tapi saya pikir itu benar.

Saya telah melaporkan bug sebagai PR 93982 . Perbaikan yang diusulkan telah dilakukan tetapi tidak memperbaikinya dalam semua kasus, mengarah ke tindak lanjut PR 94015 ( tautan godbolt ).

Anda harus dapat mengatasi bug dengan kompilasi dengan bendera -fno-optimize-strlen.


Saya dapat mengurangi test case Anda menjadi contoh minimal berikut (juga pada 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;
}

Dengan gcc trunk (gcc versi 10.0.1 20200225 (percobaan)) dan -O2(semua opsi lain ternyata tidak diperlukan), rakitan yang dihasilkan pada amd64 adalah sebagai berikut:

.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

Jadi Anda benar bahwa kompiler gagal diinisialisasi res.target[1](perhatikan tidak adanya yang mencolok dari movq $.LC1, 8(%rax)).

Sangat menarik untuk bermain dengan kode dan melihat apa yang mempengaruhi "bug". Mungkin secara signifikan, mengubah tipe kembali R_allocuntuk void *membuatnya hilang, dan memberi Anda output perakitan yang "benar". Mungkin kurang signifikan tetapi lebih menyenangkan, mengubah string "12345678"menjadi lebih panjang atau lebih pendek juga membuatnya hilang.


Diskusi sebelumnya, sekarang diselesaikan - kode ini tampaknya sah.

Pertanyaan saya adalah apakah kode Anda benar-benar legal. Fakta bahwa Anda mengambil yang char *dikembalikan oleh R_alloc()dan melemparkannya ke const char **, dan kemudian menyimpan const char *sepertinya itu mungkin melanggar aturan aliasing yang ketat , karena chardan const char *bukan tipe yang kompatibel. Ada pengecualian yang memungkinkan Anda untuk mengakses objek apa pun sebagai char(untuk mengimplementasikan hal-hal seperti memcpy), tetapi ini sebaliknya, dan sejauh yang saya mengerti, itu tidak diperbolehkan. Itu membuat kode Anda menghasilkan perilaku yang tidak terdefinisi sehingga kompiler dapat secara legal melakukan apa pun yang diinginkannya.

Jika demikian, memperbaiki yang benar akan untuk R untuk mengubah kode mereka sehingga R_alloc()kembali void *bukan char *. Maka tidak akan ada masalah alias. Sayangnya, kode itu berada di luar kendali Anda, dan tidak jelas bagi saya bagaimana Anda dapat menggunakan fungsi ini sama sekali tanpa melanggar alias ketat. Solusi mungkin untuk menempatkan variabel sementara, misalnya void *tmp = R_alloc(); res.target = tmp;yang memecahkan masalah dalam kasus uji, tapi saya masih tidak yakin apakah itu legal.

Namun, saya tidak yakin dengan hipotesis "aliasing ketat" ini, karena mengkompilasi dengan -fno-strict-aliasing, yang mana AFAIK seharusnya membuat gcc mengizinkan konstruksi semacam itu, tidak membuat masalah hilang!


Memperbarui. Mencoba beberapa opsi berbeda, saya menemukan bahwa salah satu -fno-optimize-strlenatau -fno-tree-forwpropakan menghasilkan "benar" kode yang dihasilkan. Juga, menggunakan -O1 -foptimize-strlenmenghasilkan kode yang salah (tetapi -O1 -ftree-forwproptidak).

Setelah sedikit git bisectlatihan, kesalahan tampaknya telah diperkenalkan di commit 34fcf41e30ff56155e996f5e04 .


Pembaruan 2. Saya mencoba menggali ke sumber gcc sedikit, hanya untuk melihat apa yang bisa saya pelajari. (Saya tidak mengklaim sebagai ahli kompiler apa pun!)

Sepertinya kode di tree-ssa-strlen.cdimaksudkan untuk melacak string yang muncul di program. Sejauh yang saya tahu, bug adalah bahwa dalam melihat pernyataan res.target[0] = "12345678";kompiler mengonfigurasi alamat string literal "12345678"dengan string itu sendiri. (Itu tampaknya terkait dengan kode mencurigakan ini yang ditambahkan dalam komit yang disebutkan di atas, di mana jika ia mencoba untuk menghitung byte dari sebuah "string" yang sebenarnya merupakan alamat, ia malah melihat apa yang ditunjukkan oleh alamat itu.)

Jadi berpikir bahwa pernyataan res.target[0] = "12345678", bukan menyimpan alamat dari "12345678"di alamat res.target, adalah menyimpan string itu sendiri di alamat itu, seperti jika pernyataan itu strcpy(res.target, "12345678"). Catatan untuk apa yang ada di depan bahwa ini akan mengakibatkan trailing nul disimpan di alamat res.target+8(pada tahap ini di kompiler, semua offset dalam byte).

Sekarang ketika kompilator melihat res.target[1] = "", ia juga memperlakukan ini seolah-olah itu strcpy(res.target+8, ""), 8 berasal dari ukuran a char *. Artinya, seolah-olah itu hanya menyimpan byte byte di alamat res.target+8. Namun, kompiler "tahu" bahwa pernyataan sebelumnya sudah menyimpan byte di alamat itu! Dengan demikian, pernyataan ini "berlebihan" dan dapat dibuang (di sini ).

Ini menjelaskan mengapa string harus panjangnya 8 karakter untuk memicu bug. (Meskipun kelipatan 8 lainnya juga dapat memicu bug dalam situasi lain.)

Nate Eldredge
sumber
Pembentukan kembali FWIW ke berbagai jenis pointer didokumentasikan . Saya tidak tahu tentang aliasing untuk mengetahui apakah int*boleh kembali tetapi tidak const char**.
BrodieG
Jika pemahaman saya tentang aliasing ketat benar, maka para pemain int *juga ilegal (atau lebih tepatnya, benar-benar menyimpan intdi sana ilegal).
Nate Eldredge
1
Ini tidak ada hubungannya dengan aturan aliasing yang ketat. Aturan aliasing ketat adalah tentang mengakses data yang sudah Anda simpan menggunakan pegangan yang berbeda. Karena Anda hanya menetapkan di sini, itu tidak menyentuh aturan aliasing yang ketat. Casting pointer valid ketika kedua tipe pointer memiliki persyaratan alignment yang sama, tetapi di sini Anda casting dari char*dan bekerja pada x86_64 ... Saya tidak melihat UB di sini, ini bug gcc.
KamilCuk
1
Ya dan tidak, @KamilCuk. Dalam terminologi standar, "mengakses" termasuk membaca dan memodifikasi nilai suatu objek. Oleh karena itu, aturan aliasing yang ketat adalah "menyimpan". Ini tidak terbatas pada operasi read-back. Tetapi untuk objek tanpa tipe yang dideklarasikan, itu diperdebatkan oleh fakta bahwa menulis ke objek seperti itu secara otomatis mengubah tipe efektifnya agar sesuai dengan apa yang ditulis. Objek tanpa tipe yang dideklarasikan adalah objek yang dialokasikan secara dinamis (terlepas dari jenis pointer yang diaksesnya), jadi memang tidak ada pelanggaran SA di sini.
John Bollinger
2
Ya, @Nate, dengan definisi tersebut R_alloc(), programnya sudah sesuai, terlepas dari unit terjemahan mana yang R_alloc()didefinisikan. Ini adalah kompiler yang gagal untuk menyesuaikan di sini.
John Bollinger