Saya ingin menyiapkan sedikit alat pendidikan untuk SO yang akan membantu programmer pemula (dan menengah) untuk mengenali dan menantang asumsi mereka yang tidak beralasan di C, C ++ dan platform mereka.
Contoh:
- "bilangan bulat membungkus"
- "setiap orang memiliki ASCII"
- "Saya dapat menyimpan penunjuk fungsi dalam kekosongan *"
Saya membayangkan bahwa program pengujian kecil dapat dijalankan pada berbagai platform, yang menjalankan asumsi "masuk akal" yang, dari pengalaman kami di SO, biasanya dibuat oleh banyak pengembang arus utama yang tidak berpengalaman / setengah berpengalaman dan mencatat cara mereka merusak mesin yang beragam.
Tujuannya bukan untuk membuktikan bahwa melakukan sesuatu "aman" (yang tidak mungkin dilakukan, tes hanya membuktikan apa saja jika rusak), tetapi untuk menunjukkan kepada individu yang paling tidak mengerti bagaimana ekspresi yang paling tidak mencolok merusak mesin lain, jika memiliki perilaku yang tidak ditentukan atau implementasi yang ditentukan. .
Untuk mencapai ini saya ingin bertanya kepada Anda:
- Bagaimana ide ini dapat ditingkatkan?
- Tes mana yang akan baik dan bagaimana seharusnya?
- Apakah Anda akan menjalankan tes pada platform yang bisa Anda dapatkan dan memposting hasilnya, sehingga kami mendapatkan database platform, bagaimana perbedaannya dan mengapa perbedaan ini diperbolehkan?
Inilah versi saat ini untuk mainan uji:
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <stddef.h>
int count=0;
int total=0;
void expect(const char *info, const char *expr)
{
printf("..%s\n but '%s' is false.\n",info,expr);
fflush(stdout);
count++;
}
#define EXPECT(INFO,EXPR) if (total++,!(EXPR)) expect(INFO,#EXPR)
/* stack check..How can I do this better? */
ptrdiff_t check_grow(int k, int *p)
{
if (p==0) p=&k;
if (k==0) return &k-p;
else return check_grow(k-1,p);
}
#define BITS_PER_INT (sizeof(int)*CHAR_BIT)
int bits_per_int=BITS_PER_INT;
int int_max=INT_MAX;
int int_min=INT_MIN;
/* for 21 - left to right */
int ltr_result=0;
unsigned ltr_fun(int k)
{
ltr_result=ltr_result*10+k;
return 1;
}
int main()
{
printf("We like to think that:\n");
/* characters */
EXPECT("00 we have ASCII",('A'==65));
EXPECT("01 A-Z is in a block",('Z'-'A')+1==26);
EXPECT("02 big letters come before small letters",('A'<'a'));
EXPECT("03 a char is 8 bits",CHAR_BIT==8);
EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);
/* integers */
EXPECT("05 int has the size of pointers",sizeof(int)==sizeof(void*));
/* not true for Windows-64 */
EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));
EXPECT("06 integers are 2-complement and wrap around",(int_max+1)==(int_min));
EXPECT("07 integers are 2-complement and *always* wrap around",(INT_MAX+1)==(INT_MIN));
EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);
{
int t;
EXPECT("09a minus shifts backwards",(t=-1,(15<<t)==7));
}
/* pointers */
/* Suggested by jalf */
EXPECT("10 void* can store function pointers",sizeof(void*)>=sizeof(void(*)()));
/* execution */
EXPECT("11 Detecting how the stack grows is easy",check_grow(5,0)!=0);
EXPECT("12 the stack grows downwards",check_grow(5,0)<0);
{
int t;
/* suggested by jk */
EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));
}
{
/* Suggested by S.Lott */
int a[2]={0,0};
int i=0;
EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));
}
{
struct {
char c;
int i;
} char_int;
EXPECT("15 structs are packed",sizeof(char_int)==(sizeof(char)+sizeof(int)));
}
{
EXPECT("16 malloc()=NULL means out of memory",(malloc(0)!=NULL));
}
/* suggested by David Thornley */
EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));
/* this is true for C99, but not for C90. */
EXPECT("18 a%b has the same sign as a",((-10%3)==-1) && ((10%-3)==1));
/* suggested by nos */
EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
EXPECT("19-3 int<long",sizeof(int)<sizeof(long));
EXPECT("20 ptrdiff_t and size_t have the same size",(sizeof(ptrdiff_t)==sizeof(size_t)));
#if 0
{
/* suggested by R. */
/* this crashed on TC 3.0++, compact. */
char buf[10];
EXPECT("21 You can use snprintf to append a string",
(snprintf(buf,10,"OK"),snprintf(buf,10,"%s!!",buf),strcmp(buf,"OK!!")==0));
}
#endif
EXPECT("21 Evaluation is left to right",
(ltr_fun(1)*ltr_fun(2)*ltr_fun(3)*ltr_fun(4),ltr_result==1234));
{
#ifdef __STDC_IEC_559__
int STDC_IEC_559_is_defined=1;
#else
/* This either means, there is no FP support
*or* the compiler is not C99 enough to define __STDC_IEC_559__
*or* the FP support is not IEEE compliant. */
int STDC_IEC_559_is_defined=0;
#endif
EXPECT("22 floating point is always IEEE",STDC_IEC_559_is_defined);
}
printf("From what I can say with my puny test cases, you are %d%% mainstream\n",100-(100*count)/total);
return 0;
}
Oh, dan saya membuat wiki komunitas ini sejak awal karena saya pikir orang-orang ingin mengedit pengobrol saya ketika mereka membaca ini.
UPDATE Terima kasih atas masukan Anda. Saya telah menambahkan beberapa kasus dari jawaban Anda dan akan melihat apakah saya dapat menyiapkan github untuk ini seperti yang disarankan Greg.
UPDATE : Saya telah membuat repo github untuk ini, file tersebut adalah "gotcha.c":
Harap jawab di sini dengan tambalan atau ide baru, sehingga dapat didiskusikan atau diklarifikasi di sini. Saya akan menggabungkannya menjadi gotcha.c.
sumber
dlsym()
mengembalikan void * tetapi ditujukan untuk penunjuk data dan fungsi. Oleh karena itu mungkin tidak terlalu buruk untuk bergantung pada ini.Jawaban:
Urutan evaluasi subekspresi, termasuk
+
,-
,=
,*
,/
), dengan pengecualian:&&
dan||
),?:
), dan,
)tidak ditentukan
Sebagai contoh
sumber
boost::spirit
)+
operator tidak ditentukan (Penulis kompiler tidak perlu mendokumentasikan perilaku tersebut). Itu tidak melanggar aturan titik urutan seperti itu.sdcc 29.7 / ucSim / Z80
printf macet. "O_O"
gcc 4.4@x86_64-suse-linux
gcc 4.4@x86_64-suse-linux (-O2)
dentang 2.7@x86_64-suse-linux
open64 4.2.3@x86_64-suse-linux
intel 11.1@x86_64-suse-linux
Turbo C ++ / DOS / Memori Kecil
Turbo C ++ / DOS / Memori Sedang
Turbo C ++ / DOS / Memori Kompak
cl65 @ Commodore PET (wakil emulator)
Saya akan memperbaruinya nanti:
Borland C ++ Builder 6.0 pada Windows XP
Visual Studio Express 2010 C ++ CLR, Windows 7 64bit
(harus dikompilasi sebagai C ++ karena compiler CLR tidak mendukung C murni)
MINGW64 (gcc-4.5.2 prapelase)
- http://mingw-w64.sourceforge.net/
64 bit Windows menggunakan model LLP64: Keduanya
int
danlong
didefinisikan sebagai 32-bit, yang berarti keduanya tidak cukup panjang untuk sebuah pointer.avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)
Asumsi yang gagal adalah:
Atmega168 memiliki PC 16 bit, tetapi kode dan data berada di ruang alamat terpisah. Atmegas yang lebih besar memiliki PC 22 bit !.
gcc 4.2.1 di MacOSX 10.6, dikompilasi dengan -arch ppc
sumber
sizeof(void*)>=sizeof(void(*)())
akan lebih relevan daripada ==. Semua yang kita pedulikan adalah "dapat kita menyimpan fungsi pointer dalam pointer kekosongan", sehingga asumsi yang Anda butuhkan untuk menguji adalah apakahvoid*
ini setidaknya sebagai besar sebagai pointer fungsi.sizeof(void*)>=sizeof(void(*)())
- lihat opengroup.org/onlinepubs/009695399/functions/dlsym.htmlDahulu kala, saya mengajar C dari buku teks yang dimiliki
sebagai contoh pertanyaan. Gagal untuk siswa, karena
sizeof
menghasilkan nilai tipesize_t
, bukanint
,int
pada implementasi ini adalah 16 bit dansize_t
32, dan itu adalah big-endian. (Platformnya adalah Lightspeed C pada Macintosh berbasis 680x0. Saya bilang sudah lama sekali.)sumber
unsigned long long
ada di sana. Ditambahkan sebagai Test 17.z
pengubah untuksize_t
bilangan bulat berukuran, danlong long
juga tidak didukung pada beberapa platform. Jadi tidak ada cara portabel yang aman untuk memformat atau mentransmisikan ukuran cetakan suatu objek.Anda perlu memasukkan asumsi
++
dan--
asumsi yang dibuat orang.Misalnya, secara sintaksis legal, tetapi menghasilkan hasil yang bervariasi bergantung pada terlalu banyak hal untuk dipikirkan.
Pernyataan apa pun yang memiliki
++
(atau--
) dan variabel yang muncul lebih dari sekali merupakan masalah.sumber
Sangat menarik!
Hal-hal lain yang menurut saya mungkin berguna untuk diperiksa:
apakah penunjuk fungsi dan penunjuk data ada di ruang alamat yang sama? (Kerusakan pada mesin arsitektur Harvard seperti mode kecil DOS. Namun, tidak tahu bagaimana Anda akan mengujinya.)
jika Anda mengambil penunjuk data NULL dan melemparkannya ke tipe integer yang sesuai, apakah ia memiliki nilai numerik 0? (Kerusakan pada beberapa mesin yang sangat kuno --- lihat http://c-faq.com/null/machexamp.html .) Ditto dengan function pointer. Juga, mereka mungkin memiliki nilai yang berbeda.
apakah menaikkan pointer melewati akhir objek penyimpanan yang sesuai, dan kemudian kembali lagi, menyebabkan hasil yang masuk akal? (Saya tidak tahu mesin apa pun yang benar-benar rusak, tetapi saya yakin spesifikasi C tidak memungkinkan Anda untuk berpikir tentang pointer yang tidak menunjuk ke (a) isi array atau (b) elemen segera setelah array atau (c) NULL Lihat http://c-faq.com/aryptr/non0based.html .)
apakah membandingkan dua pointer ke objek penyimpanan yang berbeda dengan <dan> menghasilkan hasil yang konsisten? (Saya dapat membayangkan ini melanggar pada mesin berbasis segmen eksotis; spek melarang perbandingan seperti itu, sehingga kompiler berhak untuk membandingkan bagian offset dari pointer saja, dan bukan bagian segmen.)
Hmm. Saya akan mencoba dan memikirkan lebih banyak lagi.
Sunting: Menambahkan beberapa tautan klarifikasi ke C FAQ yang sangat baik.
sumber
Saya pikir Anda harus berusaha untuk membedakan antara dua kelas asumsi "salah" yang sangat berbeda. Setengah yang baik (pergeseran kanan dan ekstensi tanda, pengkodean yang kompatibel dengan ASCII, memori linier, penunjuk data dan fungsi kompatibel, dll.) Adalah asumsi yang cukup masuk akal untuk dibuat oleh sebagian besar pembuat kode C, dan bahkan mungkin dimasukkan sebagai bagian dari standar jika C sedang dirancang hari ini dan jika kami tidak memiliki warisan IBM junk kakek-in. Separuh lainnya (hal-hal yang terkait dengan aliasing memori, perilaku fungsi pustaka ketika memori input dan output tumpang tindih, asumsi 32-bit seperti pointer itu cocok
int
atau yang dapat Anda gunakanmalloc
tanpa prototipe, konvensi pemanggilan itu identik untuk fungsi variadic dan non-variadic, ...) baik konflik dengan pengoptimalan yang ingin dilakukan kompiler modern atau dengan migrasi ke mesin 64-bit atau teknologi baru lainnya.sumber
malloc
tanpa prototipe berarti tidak termasuk<stdlib.h>
, yang menyebabkanmalloc
default keint malloc(int)
, tidak-tidak jika Anda ingin mendukung 64-bit.<stdlib.h>
selama Anda menyertakan tajuk lain yang menentukansize_t
dan Anda kemudian menyatakan sendirimalloc
dengan prototipe yang benar.Ini yang menyenangkan: Apa yang salah dengan fungsi ini?
[Answer (rot13): Inevnqvp nethzragf borl gur byq X&E cebzbgvba ehyrf, juvpu zrnaf lbh pnaabg hfr 'sybng' (be 'pune' be 'fubeg') va in_net! Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe. (TPP qbrf rzvg n jneavat, gubhtu.)]
sumber
Satu lagi tentang mode teks dalam
fopen
. Kebanyakan pemrogram berasumsi bahwa teks dan biner adalah sama (Unix) atau mode teks menambahkan\r
karakter (Windows). Tetapi C telah di-porting ke sistem yang menggunakan record dengan lebar tetap, yangfputc('\n', file)
pada file teks berarti menambahkan spasi atau sesuatu sampai ukuran file adalah kelipatan dari panjang record.Dan inilah hasil saya:
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 di x86-64
sumber
pow(2, n)
dengan operasi bit.Beberapa di antaranya tidak dapat dengan mudah diuji dari dalam C karena program tersebut kemungkinan besar akan mogok pada implementasi yang asumsi tidak berlaku.
"Tidak apa-apa melakukan apa pun dengan variabel bernilai pointer. Ini hanya perlu berisi nilai pointer yang valid jika Anda membedakannya."
Sama dengan tipe integral dan floating point (selain
unsigned char
), yang diperbolehkan memiliki representasi trap."Perhitungan bilangan bulat membungkus. Jadi program ini mencetak bilangan bulat negatif yang besar."
(C89 saja.) "Tidak apa-apa untuk jatuh dari akhir
main
."sumber
gcc -ftrapv -O
, hasilnyaWe like to think that:
diikuti olehAborted
main
tanpa nilai: program benar tetapi mengembalikan status penghentian yang tidak ditentukan (C89 §2.1.2.2). Dengan banyak implementasi (seperti gcc, dan kompiler unix yang lebih lama) Anda mendapatkan apa pun yang ada di register tertentu pada saat itu. Program ini biasanya bekerja sampai digunakan di makefile atau lingkungan lain yang memeriksa status penghentian.Nah, asumsi portabilitas klasik yang belum disebutkan adalah
sumber
short
nilai fedcab9876543210 (yaitu 16 digit biner) sebagai dua byte 0248ace dan fdb97531.Kesalahan diskritisasi karena representasi floating point. Misalnya, jika Anda menggunakan rumus standar untuk menyelesaikan persamaan kuadrat, atau perbedaan hingga untuk memperkirakan turunan, atau rumus standar untuk menghitung varian, presisi akan hilang karena penghitungan perbedaan antara bilangan serupa. Algoritma Gauß untuk menyelesaikan sistem linier adalah buruk karena kesalahan pembulatan menumpuk, sehingga seseorang menggunakan dekomposisi QR atau LU, dekomposisi Cholesky, SVD, dll. Penambahan bilangan floating point tidak bersifat asosiatif. Ada nilai denormal, tak terbatas dan NaN. a + b - a ≠ b .
String: Perbedaan antara karakter, poin kode, dan unit kode. Bagaimana Unicode diimplementasikan pada berbagai sistem operasi; Pengkodean unicode. Membuka file dengan nama file Unicode yang berubah-ubah tidak dimungkinkan dengan C ++ secara portabel.
Kondisi balapan, bahkan tanpa threading: jika Anda menguji apakah sebuah file ada, hasilnya bisa menjadi tidak valid kapan saja.
ERROR_SUCCESS
= 0sumber
Sertakan centang untuk ukuran integer. Kebanyakan orang berasumsi bahwa int lebih besar dari short lebih besar dari char. Namun, ini semua mungkin salah:
sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)
Kode ini mungkin gagal (macet ke akses yang tidak selaras)
sumber
int *p = (int*)&buf[1];
di c ++, orang-orang berharap itu berhasil juga.sizeof(char) < sizeof(int)
diperlukan. Misalnya, fgetc () mengembalikan nilai karakter sebagai karakter unsigned yang dikonversi menjadi int, atauEOF
yang merupakan nilai negatif.unsigned char
mungkin tidak memiliki bit padding, jadi satu-satunya cara untuk melakukannya adalah dengan membuat int lebih besar dari char. Selain itu, (sebagian besar versi) spesifikasi C mengharuskan nilai apa pun dari kisaran -32767..32767 dapat disimpan di int.Beberapa hal tentang tipe data bawaan:
char
dansigned char
sebenarnya adalah dua jenis yang berbeda (tidak sepertiint
dansigned int
yang mengacu pada jenis bilangan bulat bertanda yang sama).-3/5
bisa kembali0
atau-1
. Pembulatan ke arah nol jika satu operan negatif hanya dijamin di C99 ke atas dan C ++ 0x ke atas.int
memiliki setidaknya 16 bit, along
memiliki setidaknya 32 bit, along long
memiliki setidaknya 64 bit. Afloat
setidaknya dapat mewakili 6 digit desimal paling signifikan dengan benar. SEBUAHdouble
setidaknya dapat mewakili 10 digit desimal paling signifikan dengan benar.Memang, pada kebanyakan mesin kita akan memiliki dua komplemen dan IEEE 754 float.
sumber
int mult(int a,int b) { return (long)a*b;}
[misalnya jikaint
32 bit, tetapi register danlong
64]. Tanpa persyaratan seperti itu, perilaku "alami" dari implementasi tercepatlong l=mult(1000000,1000000);
akan ditetapkanl
sama dengan1000000000000
, meskipun itu adalah nilai yang "tidak mungkin" untuk sebuahint
.Bagaimana dengan yang ini:
Tidak ada penunjuk data yang bisa sama dengan penunjuk fungsi yang valid.
Ini BENAR untuk semua model datar, MS-DOS TINY, LARGE, dan model BESAR, salah untuk model MS-DOS KECIL, dan hampir selalu salah untuk model MEDIUM dan COMPACT (tergantung pada alamat pemuatan, Anda memerlukan DOS yang sangat lama untuk buat itu benar).
Saya tidak bisa menulis tes untuk ini
Dan lebih buruk: petunjuk yang dicor ke ptrdiff_t dapat dibandingkan. Ini tidak benar untuk model MS-DOS BESAR (satu-satunya perbedaan antara LARGE dan HUGE adalah BESAR menambahkan kode kompiler untuk menormalkan petunjuk).
Saya tidak dapat menulis tes karena lingkungan di mana bom ini keras tidak akan mengalokasikan buffer yang lebih besar dari 64K sehingga kode yang menunjukkannya akan macet di platform lain.
Tes khusus ini akan lulus pada satu sistem yang sekarang tidak berfungsi (perhatikan itu tergantung pada internal malloc):
sumber
EDIT: Diperbarui ke versi terakhir program
Solaris-SPARC
gcc 3.4.6 dalam 32 bit
gcc 3.4.6 dalam 64 bit
dan dengan SUNStudio 11 32 bit
dan dengan SUNStudio 11 64 bit
sumber
Anda dapat menggunakan mode teks (
fopen("filename", "r")
) untuk membaca segala jenis file teks.Sementara ini harus dalam pekerjaan teori baik-baik saja, jika Anda juga menggunakan
ftell()
dalam kode Anda, dan file teks Anda memiliki UNIX-style line-ujung, di beberapa versi Windows standar perpustakaan,ftell()
sering akan kembali nilai-nilai yang tidak valid. Solusinya adalah dengan menggunakan mode biner sebagai gantinya (fopen("filename", "rb")
).sumber
gcc 3.3.2 di AIX 5.3 (ya, kita perlu memperbarui gcc)
sumber
Asumsi yang mungkin dilakukan beberapa orang di C ++ adalah bahwa a
struct
terbatas pada apa yang dapat dilakukannya di C. Faktanya adalah, di C ++, astruct
seperti aclass
kecuali ia memiliki semua yang bersifat publik secara default.Struktur C ++:
sumber
Fungsi matematika standar pada sistem yang berbeda tidak memberikan hasil yang identik.
sumber
Visual Studio Express 2010 pada 32-bit x86.
sumber
Melalui Codepad.org (
C++: g++ 4.1.2 flags: -O -std=c++98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused -Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length=0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors -fstrict-aliasing -fstack-protector-all -Winvalid-pch
).Perhatikan bahwa Codepad tidak memiliki
stddef.h
. Saya menghapus tes 9 karena codepad menggunakan peringatan sebagai kesalahan. Saya juga mengganti namacount
variabel karena sudah ditentukan karena beberapa alasan.sumber
Bagaimana dengan pengalihan kanan dengan jumlah yang berlebihan - apakah itu diperbolehkan oleh standar, atau layak untuk diuji?
Apakah Standar C menentukan perilaku program berikut:
Pada setidaknya satu kompiler yang saya gunakan, kode itu akan gagal kecuali argumen ke print_string adalah "char const *". Apakah standar mengizinkan pembatasan seperti itu?
Beberapa sistem memungkinkan seseorang untuk menghasilkan pointer ke 'int' yang tidak selaras dan yang lainnya tidak. Mungkin layak untuk diuji.
sumber
<<
dan>>
). C99 memiliki bahasa yang identik di §6.5.7-3.putch
(mengapa Anda tidak menggunakan standarputchar
?), Saya tidak dapat melihat perilaku tidak terdefinisi dalam program Anda. C89 §3.1.4 menetapkan bahwa "literal string karakter memiliki […] tipe 'array of char'" (catatan: tidakconst
), dan bahwa "jika program mencoba mengubah literal string […], perilaku tidak ditentukan" . Kompiler apa itu, dan bagaimana cara menerjemahkan program ini?FYI, Bagi mereka yang harus menerjemahkan keterampilan C mereka ke Java, berikut adalah beberapa gotcha.
Di Java, char 16-bit dan ditandatangani. byte adalah 8-bit dan ditandatangani.
long selalu 64-bit, referensi bisa 32-bit atau 64-bit (jika Anda memiliki lebih dari aplikasi dengan lebih dari 32 GB) 64-bit JVM biasanya menggunakan referensi 32-bit.
Pergeseran ditutup sehingga i << 64 == i == i << -64, i << 63 == i << -1
ByteOrder.nativeOrder () bisa BIG_ENDIAN atau LITTLE_ENDIAN
i = i++
tidak pernah berubahi
Ukuran koleksi dan array selalu 32-bit terlepas dari apakah JVM itu 32-bit atau 64-bit.
char 16-bit, pendek 16-bit, int 32-bit dan panjang 64-bit.
sumber