İşlev işaretçileri ve veri işaretçileri neden C / C ++ ile uyumsuz?


130

Bir işlev işaretçisini bir veri işaretçisine dönüştürmenin ve bunun tersinin çoğu platformda çalıştığını, ancak çalışmasının garanti edilmediğini okudum. Bu neden böyle? Her ikisinin de basitçe ana belleğe adres olması ve dolayısıyla uyumlu olması gerekmez mi?


16
POSIX'te tanımlanan standart C'de tanımsız. Farka dikkat edin.
ephemient

Bu konuda biraz yeniyim, ancak "=" işaretinin sağ tarafındaki oyuncu kadrosunu yapmanız gerekmiyor mu? Bana öyle geliyor ki, sorun bir boşluk göstericisine atamış olmanız. Ama man sayfasının bunu yaptığını görüyorum, bu yüzden umarım birisi beni eğitebilir. ' Dlsym'den
JasonWoof

9
Veri Türleri bölümünde POSIX'in ne dediğine dikkat edin : §2.12.3 İşaretçi Türleri. Tüm işlev işaretçi türleri, tür işaretçisi ile aynı temsiliyete sahip olacaktır void. Bir işlev işaretçisinin biçimine dönüştürülmesi void *gösterimi değiştirmeyecektir. birvoid *Böyle bir dönüşümden kaynaklanan değer, bilgi kaybı olmaksızın açık bir dönüşüm kullanılarak orijinal işlev işaretçisi tipine geri dönüştürülebilir. Not : ISO C standardı bunu gerektirmez, ancak POSIX uyumluluğu için gereklidir.
Jonathan Leffler

2
bu web sitesinin HAKKINDA bölümündeki soru bu .. :) :) Sorunuzu burada görün
ZooZ

1
@KeithThompson: dünya değişiyor ve POSIX de değişiyor. 2012'de yazdıklarım artık 2018'de geçerli değil. POSIX standardı lafı değiştirdi. Artık şu ile ilişkilidir dlsym()- 'Uygulama Kullanımı' bölümünün sonuna dikkat edin: '' gibi bir void *işaretçiden bir işlev işaretçisine dönüşümün : fptr = (int (*)(int))dlsym(handle, "my_function"); ISO C standardı tarafından tanımlanmadığını unutmayın. Bu standart, bu dönüşümün uyumlu uygulamalarda doğru çalışmasını gerektirir.
Jonathan Leffler

Yanıtlar:


171

Bir mimarinin kod ve verileri aynı bellekte depolaması gerekmez. Harvard mimarisi ile kod ve veriler tamamen farklı bir bellekte saklanır. Çoğu mimari, aynı bellekte kod ve veri bulunan Von Neumann mimarileridir, ancak C kendisini mümkünse yalnızca belirli mimari türleriyle sınırlamaz.


15
Ayrıca, kod ve veriler fiziksel donanımda aynı yerde saklansa bile, yazılım ve bellek erişimi çoğu zaman verilerin işletim sistemi "onayı" olmadan kod olarak çalıştırılmasını engeller. DEP ve benzerleri.
Michael Graczyk

15
En azından farklı adres alanlarına sahip olmak kadar önemli (belki daha da önemli), işlev işaretçilerinin veri işaretçilerinden farklı bir gösterime sahip olabilmesidir.
Michael Burr

14
Farklı adres alanlarını kullanan kod ve veri işaretçilerine sahip olmak için bir Harvard mimarisine sahip olmanız bile gerekmez - eski DOS "Küçük" bellek modeli bunu yaptı (işaretçilerin yanında CS != DS).
caf

1
işletim sistemi bir yere kod yazmanıza izin verse bile, modern işlemciler bile talimat ve veri önbelleği tipik olarak ayrı ayrı ele alındığı için bu tür bir karışımla mücadele edebilir.
PypeBros

3
@EricJ. Siz çağırana kadar VirtualProtect, bu, veri bölgelerini yürütülebilir olarak işaretlemenize olanak tanır.
Dietrich Epp

37

Bazı bilgisayarların kod ve veriler için ayrı adres alanları vardır (vardı). Böyle bir donanımda işe yaramıyor.

Dil, yalnızca mevcut masaüstü uygulamaları için değil, aynı zamanda geniş bir donanım setinde uygulanmasına izin verecek şekilde tasarlanmıştır.


Görünüşe göre C dili komitesi hiçbir zaman void*işlev gösterme amacı taşımıyordu, sadece nesnelere genel bir işaretçi istiyorlardı.

C99 Gerekçesi diyor ki:

6.3.2.3 İşaretçiler
C artık çok çeşitli mimarilerde uygulanmıştır. Bu mimarilerin bazıları, bazı tamsayı türlerinin boyutu olan tekdüze işaretçiler içerirken, maksimum taşınabilir kod, farklı işaretçi türleri ve tam sayı türleri arasında gerekli herhangi bir uyuşmayı üstlenemez. Bazı uygulamalarda işaretçiler herhangi bir tam sayı türünden bile daha geniş olabilir.

Kullanılması void*( “işaretçi voidbir genel amacı, işaretçi türü”) C89 Komitesi bir buluştur. Bu türün benimsenmesi, ya rastgele işaretçileri (olduğu gibi fread) sessizce dönüştüren veya argüman türü tam olarak eşleşmediğinde (olduğu gibi strcmp) şikayet eden işlev prototip argümanlarını belirtme arzusuyla teşvik edildi . Nesne işaretçileri ve / veya tam sayılarla orantısız olabilecek işlevlere işaretçiler hakkında hiçbir şey söylenmez.

Not Son paragrafta işlevlere işaretçiler hakkında hiçbir şey söylenmez . Diğer işaretçilerden farklı olabilirler ve komite bunun farkındadır.


Standart, basitçe veri türlerini aynı boyutta yaparak ve birine atamanın ardından geri dönmenin aynı değerle sonuçlanacağını garanti ederek, bunlarla uğraşmadan onları uyumlu hale getirebilir. Bunu, her şeyle uyumlu tek işaretçi türü olan void * ile yaparlar.
Edward Strange

15
@CrazyEddie Bir işlev işaretçisi atayamazsınız void *.
ouah

4
İşlev işaretlerini kabul etmekte yanlış olabilirim, ancak asıl mesele kalır. Bitler bittir. Standart, farklı türlerin boyutlarının birbirlerinden gelen verileri barındırabilmesini gerektirebilir ve farklı bellek bölümlerinde kullanılsalar bile atamanın çalışmasının garantilenmesi gerekir. Bu uyumsuzluğun var olmasının nedeni, bunun standart tarafından garanti edilmemesi ve bu nedenle atamada verilerin kaybedilebilmesidir.
Edward Strange

5
Ancak sizeof(void*) == sizeof( void(*)() ), işlev işaretçilerinin ve veri işaretçilerinin farklı boyutlarda olduğu durumlarda, alan israf eder. Bu, ilk C standardının yazıldığı 80'li yıllarda yaygın bir durumdu.
Robᵩ

8
@RichardChambers: Farklı adres alanları, komutlar için 16 bit ve veri için 8 bit kullanan bir Atmel AVR gibi farklı adres genişliklerine sahip olabilir ; bu durumda, veriden (8 bit) işlev (16 bit) işaretleyicilere ve tekrar geri dönüştürmek zor olacaktır. C'nin uygulanmasının kolay olması gerekiyordu; Bu kolaylığın bir kısmı, verileri ve talimat işaretlerini birbiriyle uyumsuz bırakmaktan gelir.
John Bode

30

MS-DOS, Windows 3.1 ve daha eski sürümleri hatırlayanlar için cevap oldukça kolaydır. Bunların tümü, kod ve veri işaretçileri için değişen özellik kombinasyonları ile birkaç farklı bellek modelini desteklemek için kullanılır.

Örneğin Compact modeli için (küçük kod, büyük veri):

sizeof(void *) > sizeof(void(*)())

ve tersine Orta modelde (büyük kod, küçük veri):

sizeof(void *) < sizeof(void(*)())

Bu durumda, kod ve tarih için ayrı depolama alanınız yoktu, ancak yine de iki işaretçi arasında dönüştürme yapamazsınız (standart olmayan __near ve __far değiştiricileri kullanmanın kısaltması).

Ek olarak, işaretçiler aynı boyutta olsalar bile, aynı şeyi işaret ettiklerinin garantisi yoktur - DOS Küçük bellek modelinde, hem kod hem de verilerin işaretçilerin yakınında kullanılır, ancak farklı segmentleri işaret ederler. Dolayısıyla, bir işlev işaretçisini bir veri işaretçisine dönüştürmek, size işlevle hiçbir ilişkisi olan bir işaretçi vermez ve bu nedenle böyle bir dönüşümün bir faydası yoktur.


Ynt: "Bir işlev işaretçisini bir veri işaretçisine dönüştürmek, size işlevle hiçbir ilişkisi olan bir işaretçi vermez ve bu nedenle böyle bir dönüştürme için bir yararı yoktur": Bu tamamen takip etmez. Bir dönüştürme int*a void*gerçekten bir şey yapamaz bir işaretçi-ver, ama yine de yararlıdır dönüşümü gerçekleştirmek mümkün. (Bunun nedeni void*, herhangi bir nesne işaretçisini saklayabilmesidir , bu nedenle, ne tür tuttuklarını bilmeleri gerekmeyen genel algoritmalar için kullanılabilir. Aynı şey, izin verildiyse işlev işaretçileri için de yararlı olabilir.)
ruakh

4
@ruakh: dönüştürme durumunda int *için void *, void *orijinal olarak aynı nesneye en azından bir noktaya garanti int *yaptı - bu genel algoritmalar için kullanışlıdır, böylece erişim sivri nesneye gibi int n; memcpy(&n, src, sizeof n);. Bir işlev işaretçisini bir işlev işaretçisine dönüştürmenin işlevi void *gösteren bir işaretçi vermemesi durumunda, bu tür algoritmalar için yararlı değildir - yapabileceğiniz tek şey, void *geri bir işlev işaretçisine yeniden dönüştürmektir , böylece iyi, sadece unioniçeren a void *ve işlev göstericisini kullanın.
caf

@caf: Yeterince adil. Bunu belirttiğiniz için teşekkürler. Ve bu nedenle, void* did işlevi işleve işaret etse bile , sanırım insanların bunu aktarması kötü bir fikir olur memcpy. :-P
ruakh

Yukarıdan kopyalandı : POSIX'in Veri Türlerinde ne dediğine dikkat edin : §2.12.3 İşaretçi Türleri. Tüm işlev işaretçi türleri, tür işaretçisi ile aynı temsiliyete sahip olacaktır void. Bir işlev işaretçisinin biçimine dönüştürülmesi void *gösterimi değiştirmeyecektir. void *Böyle bir dönüşümden kaynaklanan bir değer, bilgi kaybı olmaksızın açık bir dönüşüm kullanılarak orijinal işlev işaretçisi tipine geri dönüştürülebilir. Not : ISO C standardı bunu gerektirmez, ancak POSIX uyumluluğu için gereklidir.
Jonathan Leffler

@caf Eğer sadece uygun türü bilen bir geri aramaya aktarılması gerekiyorsa , sadece gidiş dönüş güvenliğiyle ilgileniyorum, bu dönüştürülen değerlerin sahip olabileceği başka bir ilişki değil.
Deduplicator

23

Geçersiz kılınan işaretçilerin, herhangi bir tür veriye bir işaretçiyi yerleştirebilmesi gerekir - ancak bir işleve yönelik bir işaretçi olması gerekmez. Bazı sistemlerin, işlevlere işaretçiler için verilere yönelik işaretçilerden farklı gereksinimleri vardır (örneğin, veri ve kod için farklı adreslemeye sahip DSP'ler vardır, MS-DOS üzerindeki orta model, kod için 32 bit işaretçiler kullanır, ancak veriler için yalnızca 16 bit işaretçiler kullanır) .


1
ancak dlsym () işlevi void * dışında bir şey döndürmemelidir. Demek istediğim, boşluk * işlev göstericisi için yeterince büyük değilse, zaten fub'lenmiş değil miyiz?
Manav

1
@Knickerkicker: Evet, muhtemelen. Bellek hizmet veriyorsa, dlsym'den dönüş türü, muhtemelen 9 veya 10 yıl önce, OpenGroup'un e-posta listesinde tartışıldı. Hazırlık dışı, yine de (eğer bir şey varsa) ne olduğunu hatırlamıyorum.
Jerry Coffin

1
haklısın. Bu, görüşünüzün oldukça güzel (modası geçmiş olmasına rağmen) bir özeti gibi görünüyor.
Manav

31

2
@LegoStormtroopr: 21 kişinin oy artırma fikrine katılması ilginç , ancak sadece 3 kişi bunu yaptı. :-)
Jerry Coffin

13

Burada daha önce söylenenlere ek olarak, POSIX'e bakmak ilginçtir dlsym():

ISO C standardı, işlevlere işaretçilerin verilere işaretçiler olarak ileri geri gönderilebilmesini gerektirmez. Aslında, ISO C standardı, void * türündeki bir nesnenin bir işleve işaretçi tutabilmesini gerektirmez. Ancak, XSI uzantısını destekleyen uygulamalar, void * türündeki bir nesnenin bir işleve yönelik bir işaretçiyi tutabilmesini gerektirir. Bir işaretçiyi bir işleve başka bir veri türüne (void * dışında) bir göstericiye dönüştürmenin sonucu yine de tanımsızdır. ISO C standardına uyan derleyicilerin, void * işaretçisinden bir işlev işaretçisine dönüştürme girişiminde bulunulduğunda bir uyarı oluşturması gerektiğini unutmayın:

 fptr = (int (*)(int))dlsym(handle, "my_function");

Burada belirtilen sorun nedeniyle, gelecekteki bir sürüm, işlev işaretlerini döndürmek için yeni bir işlev ekleyebilir veya mevcut arayüz iki yeni işlev lehine kullanımdan kaldırılabilir: biri veri işaretçileri döndüren ve diğeri işlev işaretçileri döndüren.


Bu, bir işlevin adresini almak için dlsym kullanmanın şu anda güvenli olmadığı anlamına mı geliyor? Şu anda bunu yapmanın güvenli bir yolu var mı?
gexicide

4
Bu, şu anda POSIX'in bir ABI platformundan hem işlev hem de veri işaretçilerinin güvenli bir şekilde void*geri ve geri dönüştürülebilmesini gerektirdiği anlamına gelir .
Maxim Egorushkin

@gexicide Bu, POSIX uyumlu uygulamaların dile bir uzantı yaparak, standart kendi başına tanımsız davranışa uygulama tanımlı bir anlam kazandırdığı anlamına gelir. Hatta C99 standardının ortak uzantılarından biri olarak listelenmiştir, bölüm J.5.7 Fonksiyon işaretçisi yayınlar.
David Hammen

1
@DavidHammen Bu, dilin bir uzantısı değil, yeni bir ekstra gereksinimdir. C, void*bir işlev işaretçisi ile uyumlu olmayı gerektirmez , oysa POSIX bunu yapar.
Maxim Egorushkin

9

C ++ 11, C / C ++ ve POSIX arasındaki uzun süredir devam eden uyumsuzluğa karşı bir çözüme sahiptir dlsym(). Biri kullanabilirreinterpret_castUygulama bu özelliği desteklediği sürece bir işlev işaretçisini bir veri işaretçisine / veri işaretçisinden dönüştürmek için .

Standarttan, 5.2.10 para. 8, "bir işlev işaretçisini bir nesne işaretçisi türüne dönüştürmek veya bunun tersi koşullu olarak desteklenmektedir." 1.3.5, "koşullu olarak desteklenen" yi, "bir uygulamanın desteklemesi gerekmeyen bir program yapısı" olarak tanımlar.


Biri yapabilir, ama olmamalı. Bir uyumlu derleyici gerekir (bu da bir hata, bakınız başlatılmasında önemli bir uyarı oluşturmak -Werror). Daha iyi (ve UB olmayan) bir çözüm, (ie ) tarafından döndürülen nesneye bir işaretçi almak ve bunu bir işaretçiye işlev göstericisine dönüştürmektir . Hala uygulama tanımlı ancak artık bir uyarı / hataya neden değil . dlsymvoid**
Konrad Rudolph

3
@KonradRudolph: Katılmıyorum. "Koşullu olarak desteklenen" ifade, uyarı yapılmadan izin vermek dlsymve GetProcAddressderlemek için özel olarak yazılmıştır .
MSalters

@MSalters Ne demek "katılmıyorum"? Ya haklıyım ya da yanlış. Dlsym belgelerine açıkça diyor o “ISO C standardına uygun derleyiciler bir işlev işaretçisi bir boşluk * pointer bir dönüşüm yapmak istenildiğinde bir uyarı oluşturmak için gereklidir.” Bu, spekülasyon için fazla yer bırakmaz. Ve (KİK -pedantic) gelmez uyarır. Yine spekülasyon mümkün değil.
Konrad Rudolph

1
Takip: Sanırım şimdi anlıyorum. UB değil. Uygulama tanımlıdır. Uyarının oluşturulması gerekip gerekmediğinden hala emin değilim - muhtemelen değil. Oh iyi.
Konrad Rudolph

2
@KonradRudolph: "Yapmamalıyım" fikrine katılmadım, ki bu bir görüş. Cevap özellikle C ++ 11'den bahsetti ve sorun giderildiğinde C ++ CWG üyesiydim. C99 aslında farklı ifadelere sahiptir, koşullu olarak desteklenen bir C ++ buluşudur.
MSalters

7

Hedef mimariye bağlı olarak, kod ve veriler temelde uyumsuz, fiziksel olarak farklı bellek alanlarında depolanabilir.


"fiziksel olarak farklı" anlıyorum, ancak "temelde uyumsuz" ayrımı hakkında daha fazla ayrıntı verebilir misiniz? Soruda söylediğim gibi, herhangi bir işaretçi türü kadar büyük olması gereken boş bir işaretçi değil mi - yoksa bu benim açımdan yanlış bir varsayım mı?
Manav

@KnickerKicker: void *herhangi bir veri işaretçisini tutacak kadar büyüktür, ancak herhangi bir işlev işaretçisi olması gerekmez.
ephemient

1
geleceğe dönüş: P
SSpoke

5

undefined, mutlaka izin verilmediği anlamına gelmez, derleyici uygulayıcısının bunu istediği gibi yapmak için daha fazla özgürlüğe sahip olduğu anlamına gelebilir.

Örneğin, bazı mimarilerde bu mümkün olmayabilir - tanımsız, siz bunu yapamasanız bile onların uyumlu bir 'C' kitaplığına sahip olmalarına izin verir.


5

Başka bir çözüm:

POSIX'in işlev ve veri işaretçilerinin aynı boyut ve gösterime sahip olmasını garanti ettiğini varsayarsak (bunun için metni bulamıyorum, ancak alıntılanan örnek OP, en azından bu gereksinimi yerine getirmeyi amaçladıklarını gösteriyor ), aşağıdakiler çalışmalıdır:

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

Bu, takma ad kurallarının ihlal edilmesini önler. char [] tüm türlere takma ad vermesine izin verilen temsilden takma ad .

Yine başka bir yaklaşım:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

Ama memcpykesinlikle% 100 doğru C istiyorsanız bu yaklaşımı tavsiye ederim .


5

Farklı alan gereksinimleri olan farklı tipler olabilirler. Birine atamak, işaretçinin değerini geri alınamaz bir şekilde dilimleyebilir, böylece geri atamak farklı bir şeyle sonuçlanır.

Farklı türler olabileceğine inanıyorum çünkü standart, ihtiyaç duyulmadığında veya boyut CPU'nun onu kullanmak için fazladan şeyler yapmak zorunda kalmasına neden olabileceğinde alan tasarrufu sağlayan olası uygulamaları sınırlamak istemiyor.


3

Gerçekten taşınabilir olan tek çözüm, dlsymişlevler için kullanmak değil , bunun yerine dlsymişlev işaretçileri içeren verilere bir işaretçi elde etmek için kullanmaktır . Örneğin, kitaplığınızda:

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

ve sonra uygulamanızda:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

Bu arada, bu yine de iyi bir tasarım uygulamasıdır ve dlopendinamik bağlamayı desteklemeyen veya kullanıcı / sistem entegratörünün dinamik bağlantı kullanmak istemediği sistemlerdeki tüm modüllerin hem dinamik yüklemeyi hem de statik bağlamayı desteklemeyi kolaylaştırır .


2
Güzel! Bunun daha sürdürülebilir göründüğüne hemfikir olsam da, bunun üzerine statik bağlantıya nasıl vurduğum (bana göre) hala açık değil. Detaylandırır mısın
Manav

2
Her modülün kendi foo_moduleyapısı varsa (benzersiz adlarla), struct { const char *module_name; const struct module *module_funcs; }"yüklemek" istediğiniz modül için bu tabloyu aramak için bir dizi ve basit bir işlevle fazladan bir dosya oluşturabilir ve doğru işaretçiyi geri döndürebilirsiniz, sonra bunu kullanın yerine dlopenve dlsym.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

@R .. Doğru, ancak modül yapısını korumak zorunda olarak bakım maliyeti ekler.
user877329

3

İşlev işaretçilerinin boyut olarak veri işaretçilerinden farklı olabileceği modern bir örnek: C ++ sınıf üyesi işlev işaretçileri

Doğrudan https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/ adresinden alıntılanmıştır.

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };

Şimdi iki olası thisişaret var.

Bir üye işlevine Base1bir işaretçi Derived, her ikisi de aynı this işaretçiyi kullandığından, bir üye işlevine işaretçi olarak kullanılabilir . Ancak öğesinin bir üye işlevine Base2bir işaretçi, üye işlevine işaretçi olarak olduğu gibi kullanılamaz Derived, çünküthis göstericinin ayarlanması gerektiğinden .

Bunu çözmenin birçok yolu var. Visual Studio derleyicisinin bunu nasıl işlemeye karar verdiği aşağıda açıklanmıştır:

Çoklu miras alınan bir sınıfın üye işlevine işaretçi gerçekten bir yapıdır.

[Address of function]
[Adjustor]

Birden çok kalıtım kullanan bir sınıfın bir işaretçi-üyeye-işlevinin boyutu, bir göstericinin boyutu artı bir size_t.

tl; dr: Çoklu miras kullanırken, bir üye işlevine bir işaretçi (derleyiciye, sürüme, mimariye, vb. bağlı olarak) aslında şu şekilde depolanabilir:

struct { 
    void * func;
    size_t offset;
}

ki bu açıkça a'dan daha büyüktür void *.


2

Çoğu mimaride, tüm normal veri türlerine yönelik işaretçiler aynı gösterime sahiptir, bu nedenle veri işaretçisi türleri arasında geçiş yapmak işlemsizdir.

Bununla birlikte, işlev işaretçilerinin farklı bir temsil gerektirebileceği düşünülebilir, belki de diğer işaretçilerden daha büyükler. Eğer void * fonksiyon işaretçileri tutabiliyorsa, bu, void * 'in temsilinin daha büyük boyutta olması gerektiği anlamına gelir. Ve void * 'e / void *' den tüm veri işaretçilerinin bu ekstra kopyayı gerçekleştirmesi gerekecektir.

Birinin bahsettiği gibi, buna ihtiyacınız varsa bir sendika kullanarak başarabilirsiniz. Ancak void * 'in çoğu kullanımı yalnızca veri içindir, bu nedenle bir işlev göstericisinin depolanması gerektiğinde tüm bellek kullanımlarını artırmak zahmetli olacaktır.


-1

Bu 2012 yılından bu yana yorumladı edilmediğini biliyoruz, ama bunu eklemek yararlı olacağını düşündük yapmak olan bir mimariyi biliyor çok o mimari denetler ayrıcalık üzerinde bir çağrı beri veri ve işlevleri için uyumsuz işaretçileri ve ekstra bilgi taşır. Hiçbir miktar döküm yardımcı olmayacak. Bu var Değirmen .


Bu cevap yanlış. Örneğin, bir işlev işaretçisini bir veri işaretçisine dönüştürebilir ve ondan okuyabilirsiniz (her zamanki gibi bu adresten okuma izniniz varsa). Sonuç, örneğin x86'da olduğu kadar mantıklı.
Manuel Jacob
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.