gcc-10.0.1 Özel Segfault


23

Ben oldukça uzun bir süre nispeten kararlı ve çok çeşitli platformlar ve derleyiciler (windows / osx / debian / fedora gcc / clang) karşı test edilir C derlenmiş kod ile bir R paketi var .

Son zamanlarda paketi tekrar test etmek için yeni bir platform eklendi:

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"

Bu noktada derlenen kod derhal bu satırlar boyunca segfajlamaya başladı:

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

Optimizasyon seviyesi ile rocker/r-basedocker konteyner kullanarak sürekli segfault çoğaltmak mümkün . Daha düşük bir optimizasyon çalıştırmak sorundan kurtulur. Valgrind (hem -O0 hem de -O2), UBSAN (gcc / clang) dahil olmak üzere başka bir kurulum çalıştırmak hiçbir sorun göstermez. Ayrıca bunun altında kaldığından da eminim , ancak veri yok.gcc-10.0.1-O2gcc-10.0.0

gcc-10.0.1 -O2Sürümü çalıştırdım ve gdbbana garip görünen bir şey fark ettim:

gdb vs kod

Vurgulanan bölümü atlama iken dizilerin ikinci elemanların başlatma atlanır belirir ( R_allocetrafında sarıcı malloc; segfault R geri dönmeden önce olur R kontrol geri benlik çöp toplar). Daha sonra, başlatılmamış öğeye (gcc.10.0.1 -O2 sürümünde) erişildiğinde program çöker.

Bu sorunu açıkça öğenin kullanımına yol açan kodun her yerinde başlatarak bu düzeltildi, ama gerçekten boş bir dize için başlatılmış olması gerekirdi, ya da en azından ben kabul olurdu.

Açık bir şey mi kaçırıyorum yoksa aptalca bir şey mi yapıyorum? Hem makul olasılıkla C benim ikinci dil gereğidir gibidir uzak . Bunun şimdi biraz garip olması garip ve derleyicinin ne yapmaya çalıştığını anlayamıyorum.


GÜNCELLEME : Bu, tek başına uzun olarak yeniden rağmen Talimatlar, bu çoğaltmak debian:testingliman işçisi konteyner vardır gcc-10at gcc-10.0.1. Ayrıca, bana güvenmiyorsanız , sadece bu komutları çalıştırmayın .

Maalesef bu minimal tekrarlanabilir bir örnek değil.

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

Sonra R konsolunda, programı çalıştırmaya runbaşlamak için yazdıktan sonra gdb:

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)

GDB'de inceleme oldukça hızlı bir şekilde (doğru anlarsam) CSR_strmlen_xbaşlatılmayan dizeye erişmeye çalıştığını gösterir.

GÜNCELLEME 2 : Bu oldukça özyinelemeli bir işlevdir ve bunun üzerine dize başlatma biti birçok kez çağrılır. Bu çoğunlukla b / c tembel oluyordum, sadece özyinelemede rapor etmek istediğimiz bir şeyle karşılaştığımızda bir kez başlatılmış dizelere ihtiyacımız var, ancak bir şeyle karşılaşmak mümkün olduğunda her zaman başlatmak daha kolaydı. Bundan bahsedeceğim çünkü daha sonra göreceğiniz şey çoklu başlatmalar gösteriyor, ancak bunlardan sadece biri (muhtemelen <0x1400000001> adresine sahip) kullanılıyor.

Burada gösterdiğim şeylerin doğrudan segfault'a neden olan öğeyle ilişkili olduğunu garanti edemiyorum (aynı yasadışı adres erişimi olsa da), ancak @ nate-eldredge'nin sorduğu dizi öğesinin olmadığını gösteriyor dönüşten hemen önce veya çağrı işlevinde dönüşten hemen sonra başlatıldı. Arama işlevinin bunlardan 8'ini başlattığını ve hepsini çöp veya erişilemeyen bellekle doldurduğuna dikkat edin.

resim açıklamasını buraya girin

GÜNCELLEME 3 , söz konusu fonksiyonun sökülmesi:

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.

GÜNCELLEME 4 :

Yani, burada standardı ayrıştırmaya çalışmak, onun ilgili gibi görünen kısımlarıdır ( C11 taslağı ):

6.3.2.3 Par7 Dönüşümleri> Diğer İşlenenler> İşaretçiler

Bir nesne türüne işaretçi, farklı bir nesne türüne işaretçi olarak dönüştürülebilir. Elde edilen işaretçi başvurulan tür için doğru hizalanmamışsa 68), davranış tanımsızdır.
Aksi takdirde, tekrar dönüştürüldüğünde, sonuç orijinal işaretçiye eşittir. Nesneye yönelik bir işaretçi bir karakter türüne işaretçiye dönüştürüldüğünde, sonuç nesnenin adreslenen en düşük baytını gösterir. Sonucun art arda artması, nesnenin boyutuna kadar, nesnenin kalan baytlarına işaretçiler verir.

6.5 Par6 İfadeleri

Bir nesnenin depolanan değerine erişim için etkili türü, varsa, bildirilen nesnenin türüdür. 87) Karakter türü olmayan bir türe sahip bir lvalue aracılığıyla bildirilmiş bir türü olmayan bir nesneye bir değer depolanırsa, lvalue türü o erişim ve daha sonraki erişimler için nesnenin etkin türü haline gelir saklanan değeri değiştirin. Bir değer, memcpy veya memmove kullanılarak bildirilmiş bir türü olmayan bir nesneye kopyalanırsa veya bir karakter türü dizisi olarak kopyalanırsa, o erişim ve değeri değiştirmeyen sonraki erişimler için değiştirilen nesnenin etkin türü varsa, değerin kopyalandığı nesnenin etkin türü. Bildirilen türü olmayan bir nesneye yapılan tüm diğer erişimlerde, nesnenin etkili türü, erişim için kullanılan lvalue türüdür.

87) Ayrılan nesnelerin bildirilen türü yok.

IIUC , hizalanması garanti R_allocedilen bir malloced bloğuna bir ofset döndürür ve ofsetten doublesonraki bloğun boyutu istenen boyuta gelir (R'ye özgü veriler için ofsetten önce ayırma da vardır). dönüşte R_allocbu işaretçiyi yayınlar (char *).

Bölüm 6.2.5 Par 29

Geçersiz bir işaretçi, bir karakter türüne işaretçi ile aynı temsil ve hizalama gereksinimlerine sahip olmalıdır. 48) Benzer şekilde, uyumlu tiplerin nitelikli veya niteliksiz versiyonlarına işaretçiler aynı temsil ve hizalama gerekliliklerine sahip olacaktır. Yapı türlerine yönelik tüm işaretçiler, birbirleriyle aynı temsil ve hizalama gereksinimlerine sahip olmalıdır.
Birlik tiplerine yönelik tüm göstergeler, birbirleriyle aynı temsil ve hizalama gereksinimlerine sahip olacaktır.
Diğer türlere işaretçiler aynı gösterim veya hizalama gereksinimlerine sahip olmak zorunda değildir.

48) Aynı gösterim ve hizalama gereklilikleri, işlevler, değiştirilebilir işlevler ve işlev sendikaları değerleri arasında değişebilirlik anlamına gelir.

Soru Yani "biz recast izin vardır (char *)için (const char **)o sonuca ve yazma (const char **)". Yukarıdakileri okuduğumda, kodun çalıştığı sistemlerde işaretçiler hizalama ile uyumlu doublehizalamaya sahip oldukları sürece , o zaman tamam.

"Katı takma adlandırma" yı ihlal ediyor muyuz? yani:

6.5 Par 7

Bir nesnenin depolanmış değerine yalnızca aşağıdaki türlerden birine sahip bir değer değeri ifadesiyle erişilir: 88)

- nesnenin etkili tipiyle uyumlu bir tip ...

88) Bu listenin amacı, bir nesnenin diğer adının örtüştüğü veya örtüştüğü durumları belirtmektir.

Peki, derleyici (veya ) ile gösterilen nesnenin etkili tipinin ne olduğunu düşünmelidir ? Muhtemelen beyan edilen tip mi , yoksa bu gerçekten belirsiz mi? Bana sadece bu durumda kapsamı aynı nesneye erişen başka bir 'değer' olmadığı için değil gibi geliyor.res.targetres.current(const char **)

Standardın bu bölümlerinden anlam çıkarmak için güçlü bir şekilde mücadele ettiğimi itiraf edeceğim.


Henüz incelenmemişse, tam olarak ne yapıldığını görmek için sökme işlemine bakmaya değer olabilir. Ve ayrıca gcc versiyonları arasındaki sökme karşılaştırması.
kaylum

2
GCC'nin gövde versiyonuyla uğraşmaya çalışmam. Eğlenmek güzel ama bir sebepten dolayı gövde deniyor. Ne yazık ki (1) kodunuza ve aynı GCC sürümüne (3) aynı mimaride sahip olan tam yapılandırmaya (2) sahip olmadan neyin yanlış olduğunu söylemek neredeyse imkansızdır. 10.0.1 bagajdan ahıra geçtiğinde bunun devam edip etmediğini kontrol etmenizi öneririm.
Marco Bonelli

1
Bir yorum daha: -mtune=nativemakinenizin sahip olduğu CPU için optimize eder. Bu, farklı test kullanıcıları için farklı olacaktır ve sorunun bir parçası olabilir. Derlemeyi çalıştırırsanız, -vmakinenizde (örneğin -mtune=skylakebilgisayarımda) hangi işlemci ailesinin olduğunu görebilmeniz gerekir .
Nate Eldredge

1
Hata ayıklama işlemlerinden söylemek hala zor. Sökme kesin olmalıdır. Hiçbir şey çıkarmanıza gerek yoktur, sadece projeyi derlerken üretilen .o dosyasını bulun ve sökün. Ayrıca disassemblegdb içindeki talimatı da kullanabilirsiniz .
Nate Eldredge

5
Her neyse, tebrikler, problemi derleyici hatası olan nadirlerden birisin.
Nate Eldredge

Yanıtlar:


22

Özet: Bu, gcc'de dize optimizasyonu ile ilgili bir hata gibi görünüyor. Bağımsız bir test çantası aşağıdadır. Başlangıçta kodun doğru olup olmadığı konusunda bazı şüphe vardı, ama bence öyle.

Hatayı PR 93982 olarak bildirdim . Önerilen bir düzeltme yapıldı, ancak her durumda düzeltmedi ve PR 94015 takibine ( godbolt bağlantısı ) yol açtı .

Bayrağını derleyerek hata etrafında çalışabilmelisin -fno-optimize-strlen.


Test durumunuzu aşağıdaki minimum örneğe indirgedim (ayrıca godbolt'ta ):

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;
}

Gcc trunk (gcc sürüm 10.0.1 20200225 (deneysel)) ve -O2(diğer tüm seçeneklerin gereksiz olduğu ortaya çıktı) ile amd64 üzerinde oluşturulan montaj aşağıdaki gibidir:

.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

Yani derleyicinin başlatılamaması konusunda haklısınız res.target[1](göze çarpan yokluğuna dikkat edin movq $.LC1, 8(%rax)).

Kod ile oynamak ve "hata" yı neyin etkilediğini görmek ilginç. Belki önemli ölçüde dönüş türünü değiştirme R_allociçin void *uzağa gitmek yapar ve "doğru" montaj çıkış verir. Belki daha az önemli ama daha eğlenceli bir şekilde, ipin "12345678"daha uzun veya daha kısa olarak değiştirilmesi de ortadan kalkar .


Önceki tartışma, şimdi çözüldü - kod görünüşe göre yasal.

Sahip olduğum soru, kodunuzun gerçekten yasal olup olmadığıdır. Çektiğiniz gerçeği char *tarafından döndürülen R_alloc()ve onu dökme const char **ve sonra saklamak bir const char *ihlali niteliğinde olabilir gibi görünüyor sıkı örtüşme kuralı olarak, charve const char *uyumlu türleri değildir. Herhangi bir nesneye char(gibi şeyleri uygulamak için memcpy) olarak erişmenizi sağlayan bir istisna vardır , ancak bu başka bir yoldur ve anladığım kadarıyla buna izin verilmez. Kodunuzun tanımlanmamış davranış üretmesini sağlar ve böylece derleyici yasal olarak istediği her şeyi yapabilir.

Bu durumda, R'nin kodunu değiştirmesi için doğru düzeltme olur R_alloc(), void *bunun yerine döndürür char *. O zaman takma sorun olmazdı. Ne yazık ki, bu kod kontrolünüz dışındadır ve bu işlevi sıkı bir takma adı ihlal etmeden nasıl kullanabileceğiniz açık değildir. Geçici bir çözüm, örneğin void *tmp = R_alloc(); res.target = tmp;test örneğindeki sorunu çözen geçici bir değişkeni arayabilir, ancak yine de yasal olup olmadığından emin değilim.

Ancak, ben birlikte derleme, çünkü emin bu "sıkı örtüşme" hipotez değilim -fno-strict-aliasingbu tür yapılar izin AFAIK yapmak gcc gerekiyordu ki, yok değil problemi ortadan!


Güncelleme. Bazı farklı seçenekleri denerken, ya -fno-optimize-strlenya -fno-tree-forwprop"doğru" kod oluşturulmasına neden olacağını buldum . Ayrıca, kullanmak -O1 -foptimize-strlenyanlış kod verir (ancak -O1 -ftree-forwpropvermez).

Küçük bir git bisectalıştırmadan sonra , hata 34fcf41e30ff56155e996f5e04 işleminde ortaya çıkmış gibi görünüyor .


Güncelleme 2. Sadece ne öğrenebileceğimi görmek için gcc kaynağını biraz araştırmaya çalıştım. (Herhangi bir derleyici uzmanı olduğunu iddia etmiyorum!)

Kodun tree-ssa-strlen.cprogramda görünen dizeleri takip etmek için yapılmış gibi görünüyor . Anlayabildiğim kadarıyla, hata ifadesine bakarken res.target[0] = "12345678";derleyicinin dize hazır bilgisinin adresini"12345678" dizenin kendisiyle sınırlamasıdır. (Bu , yukarıda belirtilen taahhütte eklenen bu şüpheli kodla ilişkili gibi görünüyor , burada aslında bir adres olan bir "dizgenin" baytlarını saymaya çalışırsa, bunun yerine bu adresin neye işaret ettiğine bakar.)

O deyimi düşünüyor Yani res.target[0] = "12345678", yerine depolama adresini ait "12345678"adreste res.targetifadesi sanki, bu adreste dize kendisini depoluyor strcpy(res.target, "12345678"). Bunun, sondaki noktanın adreste depolanmasına neden olacağına dikkat edin res.target+8(derleyicide bu aşamada, tüm ofsetler bayt cinsindendir).

Derleyici baktığında res.target[1] = "", aynı şekilde, sanki strcpy(res.target+8, "")8'in a char *. Yani, sanki adreste bir boş bayt saklıyormuş gibi res.target+8. Ancak derleyici, önceki ifadenin bu adreste zaten bir boş bayt depoladığını "bilir"! Bu itibarla, bu ifade "gereksizdir" ve atılabilir ( burada ).

Bu, hatayı tetiklemek için dizenin neden tam olarak 8 karakter uzunluğunda olması gerektiğini açıklar. (8'in diğer katları da diğer durumlarda hatayı tetikleyebilir.)


FWIW farklı tipte bir göstergeye yeniden ölçümleme belgelenir . Bunun için yeniden yapılandırmak için ok olup olmadığını bilmek aliasing bilmem int*ama değil const char**.
BrodieG

Eğer sıkı bir örtüşme anlayışım doğruysa, o zaman oyuncu kadrosu int *da yasadışıdır (ya da aslında intorada saklamak yasadışıdır).
Nate Eldredge

1
Bunun katı takma adlandırma kuralı ile ilgisi yoktur. Katı takma adlandırma kuralı, daha önce farklı bir tanıtıcı kullanarak sakladığınız verilere erişmekle ilgilidir . Sadece burada atadığınız gibi, katı takma adlandırma kuralına dokunmaz. Her iki işaretçi türü de aynı hizalama gereksinimine sahipse işaretçiler döküm geçerlidir, ancak burada x86_64'den döküm yapıyor ve üzerinde çalışıyorsunuz ... Burada UB görmüyorum, bu gcc hatası. char*
KamilCuk

1
Evet ve hayır, @ KamilCuk. Standardın terminolojisinde "erişim", bir nesnenin değerini hem okumayı hem de değiştirmeyi içerir. Bu nedenle katı örtüşme kuralı, "depolama" ile ilgilidir. Geri okuma işlemleri ile sınırlı değildir. Ancak bildirilen türü olmayan nesneler için, böyle bir nesneye yazmanın , etkin türünü otomatik olarak yazılana karşılık gelecek şekilde değiştirmesi gerçeği ile tartışılır . Bildirilen bir türü olmayan nesneler tam olarak dinamik olarak ayrılmış nesnelerdir (erişildikleri işaretçinin türüne bakılmaksızın), bu yüzden gerçekten burada SA ihlali yoktur.
John Bollinger

2
Evet, @Nate, bu tanımla birlikte R_alloc(), hangi çeviri biriminin R_alloc()tanımlandığına bakılmaksızın program uygundur . Burada uymayan derleyici.
John Bollinger
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.