Di mana MIN
dan MAX
didefinisikan dalam C, jika ada?
Apa cara terbaik untuk menerapkan ini, secara umum dan ketik seaman mungkin? (Ekstensi kompiler / builtin untuk kompiler arus utama lebih disukai.)
Di mana MIN
dan MAX
didefinisikan dalam C, jika ada?
Apa cara terbaik untuk menerapkan ini, secara umum dan ketik seaman mungkin? (Ekstensi kompiler / builtin untuk kompiler arus utama lebih disukai.)
Jawaban:
Di mana
MIN
danMAX
didefinisikan dalam C, jika ada?
Mereka bukan.
Apa cara terbaik untuk mengimplementasikannya, secara umum dan ketik seaman mungkin (ekstensi kompiler / builtin untuk kompiler arus utama lebih disukai).
Sebagai fungsi. Saya tidak akan menggunakan makro seperti #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, terutama jika Anda berencana untuk menyebarkan kode Anda. Entah menulis sendiri, gunakan sesuatu seperti standar fmax
atau fmin
, atau perbaiki makro menggunakan GCC typeof (Anda juga mendapatkan bonus typesafety) dalam ekspresi pernyataan GCC :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Semua orang mengatakan "oh saya tahu tentang evaluasi ganda, tidak ada masalah" dan beberapa bulan kemudian, Anda akan men-debug masalah paling konyol selama berjam-jam.
Perhatikan penggunaan __typeof__
bukannya typeof
:
Jika Anda menulis file header yang harus berfungsi saat disertakan dalam program ISO C, tulislah
__typeof__
alih-alihtypeof
.
decltype
kata kunci baru MSVC ++ 2010 - tetapi meskipun demikian, Visual Studio tidak dapat melakukan pernyataan majemuk dalam makro (dan decltype
C ++ pula), yaitu ({ ... })
sintaks GCC jadi saya cukup yakin itu tidak mungkin. Saya belum melihat kompiler lain mengenai masalah ini, maaf Luther: S
MAX(someUpperBound, someRandomFunction())
untuk membatasi nilai acak ke batas atas. Itu adalah ide yang mengerikan, tetapi bahkan tidak berhasil, karena yang MAX
dia gunakan memiliki masalah evaluasi ganda, jadi dia berakhir dengan angka acak yang berbeda dari yang awalnya dievaluasi.
MIN(x++, y++)
preprocessor akan menghasilkan kode berikut (((x++) < (y++)) ? (x++) : (y++))
. Jadi, x
dan y
akan bertambah dua kali.
Ini juga disediakan dalam versi GNU libc (Linux) dan FreeBSD dari sys / param.h, dan memiliki definisi yang disediakan oleh dreamlax.
Di Debian:
$ uname -sr
Linux 2.6.11
$ cat /etc/debian_version
5.0.2
$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
$ head -n 2 /usr/include/sys/param.h | grep GNU
This file is part of the GNU C Library.
Di FreeBSD:
$ uname -sr
FreeBSD 5.5-STABLE
$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
Repositori sumber ada di sini:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
juga. :) Buruk itu tidak portabel.
Ada a std::min
dan std::max
di C ++, tapi AFAIK, tidak ada yang setara di pustaka standar C. Anda dapat mendefinisikannya sendiri dengan makro seperti
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Tetapi ini menyebabkan masalah jika Anda menulis sesuatu seperti MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
itu bukan cara yang fleksibel, mengapa ???
#define MULT(x, y) x * y
. Kemudian MULT(a + b, a + b)
mengembang ke a + b * a + b
, yang mem-parsing a + (b * a) + b
karena didahulukan. Bukan itu yang dimaksudkan oleh programmer.
Hindari ekstensi kompiler non-standar dan implementasikan sebagai makro yang benar-benar aman di C standar murni (ISO 9899: 2011).
Larutan
#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
#define ENSURE_int(i) _Generic((i), int: (i))
#define ENSURE_float(f) _Generic((f), float: (f))
#define MAX(type, x, y) \
(type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
Pemakaian
MAX(int, 2, 3)
Penjelasan
Max makro menciptakan makro lain berdasarkan type
parameter. Makro kontrol ini, jika diimplementasikan untuk jenis yang diberikan, digunakan untuk memeriksa bahwa kedua parameter dari jenis yang benar. Jika type
tidak didukung, akan ada kesalahan kompiler.
Jika salah satu x atau y bukan dari jenis yang benar, akan ada kesalahan kompilator di ENSURE_
makro. Lebih banyak makro seperti itu dapat ditambahkan jika lebih banyak jenis yang didukung. Saya berasumsi bahwa hanya tipe aritmatika (integer, float, pointer dll) yang akan digunakan dan bukan struct atau array dll.
Jika semua jenis sudah benar, makro GENERIC_MAX akan dipanggil. Tanda kurung tambahan diperlukan di sekitar setiap parameter makro, sebagai tindakan pencegahan standar yang biasa saat menulis makro C.
Lalu ada masalah biasa dengan promosi tipe implisit di C. ?:
Operator menyeimbangkan operan ke-2 dan ke-3 terhadap satu sama lain. Misalnya, hasil dari GENERIC_MAX(my_char1, my_char2)
akan menjadi int
. Untuk mencegah makro dari melakukan promosi tipe yang berpotensi berbahaya seperti itu, tipe final cast ke tipe yang dimaksud digunakan.
Alasan
Kami ingin kedua parameter makro memiliki tipe yang sama. Jika salah satu dari mereka adalah tipe yang berbeda, makro tidak lagi aman, karena operator seperti ?:
akan menghasilkan promosi tipe implisit. Dan karena itu terjadi, kita juga selalu perlu mengembalikan hasil akhir ke jenis yang dimaksud seperti dijelaskan di atas.
Makro dengan hanya satu parameter bisa ditulis dengan cara yang lebih sederhana. Tetapi dengan 2 atau lebih parameter, ada kebutuhan untuk memasukkan parameter tipe tambahan. Karena sesuatu seperti ini sayangnya tidak mungkin:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Masalahnya adalah bahwa jika makro di atas disebut MAX(1, 2)
dengan dua int
, makro masih akan mencoba untuk memperluas semua skenario yang mungkin dari _Generic
daftar asosiasi. Jadi ENSURE_float
makro juga akan diperluas, meskipun tidak relevan untuk itu int
. Dan karena makro itu sengaja hanya berisi float
tipe, kode tidak akan dikompilasi.
Untuk mengatasi ini, saya membuat nama makro selama fase pra-prosesor sebagai gantinya, dengan operator ##, sehingga tidak ada makro yang diperluas secara tidak sengaja.
Contohnya
#include <stdio.h>
#define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
#define ENSURE_int(i) _Generic((i), int: (i))
#define ENSURE_float(f) _Generic((f), float: (f))
#define MAX(type, x, y) \
(type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
int main (void)
{
int ia = 1, ib = 2;
float fa = 3.0f, fb = 4.0f;
double da = 5.0, db = 6.0;
printf("%d\n", MAX(int, ia, ib)); // ok
printf("%f\n", MAX(float, fa, fb)); // ok
//printf("%d\n", MAX(int, ia, fa)); compiler error, one of the types is wrong
//printf("%f\n", MAX(float, fa, ib)); compiler error, one of the types is wrong
//printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
//printf("%f\n", MAX(float, da, db)); compiler error, one of the types is wrong
//printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
//printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
return 0;
}
GENERIC_MAX
makro adalah ide yang buruk dengan cara, Anda hanya harus mencoba GENERIC_MAX(var++, 7)
untuk mencari tahu mengapa :-) Saat ini (terutama dengan berat mengoptimalkan / compiler inlining), makro harus cukup banyak diturunkan ke bentuk yang sederhana saja. Fungsi seperti lebih baik sebagai fungsi dan yang nilai-kelompok lebih baik sebagai enumerasi.
Saya tidak berpikir bahwa itu adalah makro standar. Sudah ada fungsi standar untuk floating point, fmax
dan fmin
(dan fmaxf
untuk float, dan fmaxl
untuk double panjang).
Anda dapat menerapkannya sebagai makro selama Anda mengetahui masalah efek samping / evaluasi ganda.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Dalam kebanyakan kasus, Anda dapat menyerahkannya ke kompiler untuk menentukan apa yang Anda coba lakukan dan mengoptimalkannya sebaik mungkin. Sementara ini menyebabkan masalah ketika digunakan seperti MAX(i++, j++)
, saya ragu ada banyak kebutuhan dalam memeriksa nilai maksimum yang meningkat dalam sekali jalan. Selisih lebih dulu, lalu periksa.
Ini adalah jawaban yang terlambat, karena perkembangan yang cukup baru. Karena OP menerima jawaban yang bergantung pada ekstensi GCC (dan dentang) yang tidak portabel typeof
- atau __typeof__
untuk 'bersih' ISO C - ada solusi yang lebih baik tersedia pada gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
Manfaat nyata dari ekstensi ini adalah bahwa setiap argumen makro hanya diperluas satu kali, tidak seperti __typeof__
solusinya.
__auto_type
adalah bentuk terbatas C ++ 11's auto
. Tidak dapat (atau tidak?) Digunakan dalam kode C ++, meskipun tidak ada alasan yang baik untuk tidak menggunakan kemampuan inferensi tipe superior auto
saat menggunakan C ++ 11.
Yang mengatakan, saya berasumsi tidak ada masalah menggunakan sintaks ini ketika makro termasuk dalam ruang extern "C" { ... }
lingkup; misalnya, dari header C. AFAIK, ekstensi ini belum menemukan info caranya
clang
mulai mendukung __auto_type
sekitar 2016 (lihat tambalan ).
c-preprocessor
tag. Suatu fungsi tidak dijamin akan digarisbawahi bahkan dengan kata kunci tersebut, kecuali menggunakan sesuatu seperti __always_inline__
atribut gcc .
Saya menulis versi ini yang berfungsi untuk MSVC, GCC, C, dan C ++.
#if defined(__cplusplus) && !defined(__GNUC__)
# include <algorithm>
# define MIN std::min
# define MAX std::max
//# define TMIN(T, a, b) std::min<T>(a, b)
//# define TMAX(T, a, b) std::max<T>(a, b)
#else
# define _CHOOSE2(binoper, lexpr, lvar, rexpr, rvar) \
({ \
decltype(lexpr) lvar = (lexpr); \
decltype(rexpr) rvar = (rexpr); \
lvar binoper rvar ? lvar : rvar; \
})
# define _CHOOSE_VAR2(prefix, unique) prefix##unique
# define _CHOOSE_VAR(prefix, unique) _CHOOSE_VAR2(prefix, unique)
# define _CHOOSE(binoper, lexpr, rexpr) \
_CHOOSE2( \
binoper, \
lexpr, _CHOOSE_VAR(_left, __COUNTER__), \
rexpr, _CHOOSE_VAR(_right, __COUNTER__) \
)
# define MIN(a, b) _CHOOSE(<, a, b)
# define MAX(a, b) _CHOOSE(>, a, b)
#endif
Jika Anda membutuhkan min / maks untuk menghindari cabang yang mahal, Anda sebaiknya tidak menggunakan operator ternary, karena akan dikompilasi menjadi lompatan. Tautan di bawah ini menjelaskan metode yang berguna untuk menerapkan fungsi min / max tanpa bercabang.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@ David Titarenco memakukannya di sini , tapi biarkan saya setidaknya membersihkannya sedikit agar terlihat bagus, dan tunjukkan keduanya min()
dan max()
bersama - sama untuk membuat menyalin dan menempel dari sini lebih mudah. :)
Pembaruan 25 April 2020: Saya juga menambahkan Bagian 3 untuk menunjukkan bagaimana hal ini akan dilakukan dengan template C ++, sebagai perbandingan yang berharga bagi mereka yang belajar C dan C ++, atau beralih dari satu ke yang lain. Saya telah melakukan yang terbaik untuk menjadi teliti dan faktual dan benar untuk menjadikan jawaban ini sebagai referensi kanonis yang dapat saya kembalikan lagi dan lagi, dan saya harap Anda merasa ini sama bermanfaatnya dengan saya.
Teknik ini umumnya digunakan, dihormati oleh mereka yang tahu cara menggunakannya dengan benar, cara "de facto" dalam melakukan sesuatu, dan baik untuk digunakan jika digunakan dengan benar, tetapi buggy (pikirkan: efek samping evaluasi ganda ) jika Anda pernah lulus ekspresi termasuk penugasan variabel untuk membandingkan:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Teknik ini menghindari efek samping dan bug "evaluasi-ganda" di atas, dan karenanya dianggap sebagai cara GCC C yang superior, lebih aman, dan "lebih modern" untuk melakukan ini. Harapkan itu bekerja dengan kompiler gcc dan dentang, karena dentang, secara desain, kompatibel dengan gcc (lihat catatan dentang di bagian bawah jawaban ini).
TETAPI: Tetap berhati-hati terhadap efek " variabel shadowing ", karena ekspresi pernyataan tampaknya digarisbawahi dan karena itu TIDAK memiliki cakupan variabel lokal mereka sendiri!
#define max(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
#define min(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; \
})
Perhatikan bahwa dalam ekspresi pernyataan gcc, ekspresi terakhir dalam blok kode adalah apa yang "dikembalikan" dari ekspresi, seolah-olah itu dikembalikan dari fungsi. Dokumentasi GCC mengatakannya seperti ini:
Hal terakhir dalam pernyataan majemuk adalah ekspresi yang diikuti oleh tanda titik koma; nilai dari subekspresi ini berfungsi sebagai nilai dari keseluruhan konstruk. (Jika Anda menggunakan jenis pernyataan lain yang terakhir di dalam kurung kurawal, konstruk memiliki tipe batal, dan dengan demikian tidak ada nilainya.)
C ++ Catatan: jika menggunakan C ++, templat mungkin direkomendasikan untuk jenis konstruk ini sebagai gantinya, tetapi saya pribadi tidak menyukai templat dan mungkin akan menggunakan salah satu konstruk di atas dalam C ++, karena saya sering menggunakan dan lebih suka gaya C pada embedded C ++ juga.
Bagian ini ditambahkan 25 April 2020:
Saya telah melakukan satu ton C ++ beberapa bulan terakhir, dan tekanan untuk lebih memilih template daripada makro, jika mampu, di komunitas C ++ cukup kuat. Sebagai hasilnya, saya menjadi lebih baik dalam menggunakan templat, dan ingin memasukkan versi templat C ++ di sini untuk kelengkapan dan menjadikannya jawaban yang lebih kanonik dan menyeluruh.
Inilah versi templat fungsi dasar max()
dan min()
mungkin terlihat seperti di C ++:
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
template <typename T>
T min(T a, T b)
{
return a < b ? a : b;
}
Lakukan bacaan tambahan tentang templat C ++ di sini: Wikipedia: Templat (C ++) .
Namun, keduanya max()
dan min()
sudah menjadi bagian dari pustaka standar C ++, di <algorithm>
header ( #include <algorithm>
). Di pustaka standar C ++ mereka didefinisikan sedikit berbeda dari yang saya miliki di atas. Prototipe default untuk std::max<>()
dan std::min<>()
, misalnya, dalam C ++ 14, melihat prototipe mereka di tautan cplusplus.com tepat di atas, adalah:
template <class T>
constexpr const T& max(const T& a, const T& b);
template <class T>
constexpr const T& min(const T& a, const T& b);
Perhatikan bahwa kata kunci typename
adalah alias untuk class
(sehingga penggunaannya identik apakah Anda katakan <typename T>
atau <class T>
), karena itu kemudian mengakui setelah penemuan C ++ template, bahwa jenis template mungkin tipe biasa ( int
, float
, dll) bukan hanya tipe kelas.
Di sini Anda dapat melihat bahwa kedua jenis input, serta jenis kembali, adalah const T&
, yang berarti "referensi konstan untuk mengetik T
". Ini berarti parameter input dan nilai balik diteruskan dengan referensi alih-alih lewat nilai . Ini seperti melewati pointer, dan lebih efisien untuk tipe besar, seperti objek kelas. Bagian constexpr
dari fungsi memodifikasi fungsi itu sendiri dan menunjukkan bahwa fungsi tersebut harus mampu dievaluasi pada waktu kompilasi (setidaknya jika diberikan constexpr
parameter input), tetapi jika tidak dapat dievaluasi pada waktu kompilasi, maka defaultnya kembali ke evaluasi run-time, seperti fungsi normal lainnya.
Aspek waktu kompilasi dari constexpr
fungsi C ++ membuatnya menjadi semacam makro-C, di mana jika evaluasi waktu kompilasi dimungkinkan untuk suatu constexpr
fungsi, itu akan dilakukan pada waktu kompilasi, sama seperti substitusi makro MIN()
atau MAX()
mungkin sepenuhnya dievaluasi pada waktu kompilasi dalam C atau C ++ juga. Untuk referensi tambahan untuk info templat C ++ ini, lihat di bawah.
Dentang catatan dari Wikipedia :
[Dentang] dirancang untuk bertindak sebagai pengganti drop-in untuk GNU Compiler Collection (GCC), mendukung sebagian besar bendera kompilasi dan ekstensi bahasa tidak resmi.
Ada baiknya menunjukkan saya pikir jika Anda mendefinisikan min
dan max
dengan tersier seperti
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
kemudian untuk mendapatkan hasil yang sama untuk kasus khusus fmin(-0.0,0.0)
dan fmax(-0.0,0.0)
Anda perlu menukar argumen
fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
Sepertinya makro Windef.h
(a la #include <windows.h>
) memiliki max
dan min
(huruf kecil), yang juga mengalami kesulitan "evaluasi ganda", tetapi mereka ada di sana untuk mereka yang tidak ingin memutar ulang sendiri :)
Saya tahu orang itu berkata "C" ... Tetapi jika Anda memiliki kesempatan, gunakan templat C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Ketik aman, dan tidak ada masalah dengan ++ yang disebutkan dalam komentar lain.
Maksimal dua bilangan bulat a
dan b
adalah (int)(0.5((a+b)+abs(a-b)))
. Ini juga dapat bekerja dengan (double)
dan fabs(a-b)
untuk ganda (serupa untuk pelampung)
Cara paling sederhana adalah mendefinisikannya sebagai fungsi global dalam suatu .h
file, dan menyebutnya kapan saja Anda inginkan, jika program Anda bersifat modular dengan banyak file. Jika tidak, double MIN(a,b){return (a<b?a:b)}
adalah cara paling sederhana.
warning: expression with side-effects multiply evaluated by macro
pada titik penggunaan ...