Nerede MIN
ve nerede MAX
C tanımlanmış?
Bunları genel olarak uygulamak ve mümkün olduğunca güvenli bir şekilde yazmak için en iyi yol nedir? (Ana derleyiciler için derleyici uzantıları / yerleşimleri tercih edilir.)
Nerede MIN
ve nerede MAX
C tanımlanmış?
Bunları genel olarak uygulamak ve mümkün olduğunca güvenli bir şekilde yazmak için en iyi yol nedir? (Ana derleyiciler için derleyici uzantıları / yerleşimleri tercih edilir.)
Yanıtlar:
Nerede
MIN
ve neredeMAX
C tanımlanmış?
Onlar değil.
Bunları uygulamanın en iyi yolu, mümkün olduğunca genel ve güvenli yazın (ana derleyiciler için derleyici uzantıları / yerleşikler tercih edilir).
Fonksiyonlar olarak. #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
Özellikle kodunuzu dağıtmayı planlıyorsanız, gibi makroları kullanmam . Kendi yazınızı yazın, standart gibi bir şey kullanın fmax
ya fmin
da bir GCC deyimi ifadesinde makroyu GCC'nin typeof (daktilo güvenlik bonusu alırsınız) kullanarak düzeltin :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Herkes "ah, çift değerlendirmeyi biliyorum, sorun değil" der ve birkaç ay yolda, en sessiz problemleri saatlerce ayıklayacaksınız.
__typeof__
Bunun yerine kullanımına dikkat edin typeof
:
ISO C programlarına dahil edildiğinde çalışması gereken bir başlık dosyası yazıyorsanız,
__typeof__
bunun yerine yazıntypeof
.
decltype
anahtar kelime ile uğraşmak için denemek - ama öyle olsa bile, Visual Studio makrolarda bileşik ifadeler yapamaz (ve decltype
yine de C ++), yani GCC ({ ... })
sözdizimi, bu yüzden zaten mümkün olmadığından eminim. Bu konuyla ilgili başka bir derleyiciye bakmadım, üzgünüm Luther: S
MAX(someUpperBound, someRandomFunction())
rastgele bir değeri bir üst sınırla sınırlandırdığı bir durum gördüm . Bu korkunç bir fikirdi, ama işe yaramadı, çünkü MAX
kullandığı çift değerlendirme sorunu vardı, bu yüzden başlangıçta değerlendirilenden farklı bir rastgele sayı ile sonuçlandı.
MIN(x++, y++)
, önişlemciyi çağırırsanız aşağıdaki kodu oluşturur (((x++) < (y++)) ? (x++) : (y++))
. Yani, x
ve y
iki kez artırılacak.
Ayrıca sys / param.h'nin GNU libc (Linux) ve FreeBSD sürümlerinde sağlanır ve dreamlax tarafından sağlanan tanıma sahiptir.
Debian'da:
$ 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.
FreeBSD'de:
$ 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))
Kaynak depolar burada:
openSUSE/Linux 3.1.0-1.2-desktop
/ ' gcc version 4.6.2 (SUSE Linux)
de. :) Kötü taşınabilir değil.
C ++ 'da bir std::min
ve vardır std::max
, ancak AFAIK, C standart kütüphanesinde eşdeğer değildir. Bunları kendiniz gibi makrolarla tanımlayabilirsiniz
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Ama böyle bir şey yazarsanız sorunlara neden olur MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
Esnek bir yol olmadığını söyledikleri bir sınav buldum , neden ???
#define MULT(x, y) x * y
. Daha sonra önceliğe bağlı olarak ayrışan MULT(a + b, a + b)
genişler . Programcı muhtemelen bunu istemiyordu. a + b * a + b
a + (b * a) + b
Standart olmayan derleyici uzantılarından kaçının ve saf standart C'de tamamen güvenli bir makro olarak uygulayın (ISO 9899: 2011).
Çözüm
#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))
kullanım
MAX(int, 2, 3)
açıklama
MAX makrosu, type
parametreye dayalı olarak başka bir makro oluşturur . Bu kontrol makrosu, verilen tip için uygulanırsa, her iki parametrenin de doğru tipte olup olmadığını kontrol etmek için kullanılır. Eğer type
desteklenmiyorsa, bir derleyici hatası olacaktır.
X veya y doğru türde değilse, ENSURE_
makrolarda bir derleyici hatası olacaktır . Daha fazla tür destekleniyorsa, bu tür daha fazla makro eklenebilir. Yapılar veya diziler değil, sadece aritmetik türlerin (tamsayılar, kayan noktalar, işaretçiler vb.) Kullanılacağını varsaydım.
Tüm türler doğruysa, GENERIC_MAX makrosu çağrılır. C makroları yazarken her zamanki standart önlem olarak, her makro parametresi etrafında ekstra parantez gereklidir.
Sonra C'de örtük tür tanıtımları ile ilgili olağan problemler vardır. ?:
Operatör 2. ve 3. işlenenleri birbiriyle dengeler. Örneğin, sonucu GENERIC_MAX(my_char1, my_char2)
bir olur int
. Makronun bu tür potansiyel olarak tehlikeli tür tanıtımlarını yapmasını önlemek için, amaçlanan türe dökülen bir son tür kullanılmıştır.
gerekçe
Makroya ilişkin her iki parametrenin de aynı türde olmasını istiyoruz. Bunlardan biri farklı bir türdeyse, makro artık tür güvenli değildir, çünkü böyle bir operatör ?:
örtük tür tanıtımları verir. Ve öyle olduğu için, nihai sonucu her zaman yukarıda açıklanan amaçlanan türe geri döndürmemiz gerekir.
Sadece bir parametreli bir makro çok daha basit bir şekilde yazılabilirdi. Ancak 2 veya daha fazla parametre ile, ekstra tip parametresi eklemeye ihtiyaç vardır. Çünkü böyle bir şey maalesef imkansız:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Sorun yukarıdaki makro MAX(1, 2)
iki ile çağrılırsa int
, yine de _Generic
ilişkilendirme listesinin tüm olası senaryolarını makro genişletmeye çalışacaktır . Dolayısıyla, ENSURE_float
ilgili olmasa bile makro da genişletilecektir int
. Bu makro kasıtlı olarak yalnızca float
türü içerdiğinden kod derlenmez.
Bunu çözmek için, makro adını ## operatörü ile ön işlemci aşaması sırasında oluşturdum, böylece hiçbir makro yanlışlıkla genişletilmez.
Örnekler
#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 bu arada kötü bir fikir, sadece GENERIC_MAX(var++, 7)
nedenini bulmaya çalışmalısın :-) Günümüzde (özellikle yoğun optimizasyon / satır içi derleyicileri ile), makrolar sadece basit formlara indirgenmelidir. Fonksiyona benzer olanlar fonksiyonlar kadar, değer grubu ise numaralandırma olarak daha iyidir.
Standart makrolar olduklarını düşünmüyorum. Zaten kayan nokta için fmax
ve fmin
( fmaxf
şamandıralar ve fmaxl
uzun çiftler için) standart fonksiyonlar vardır .
Yan etkiler / çift değerlendirme konularından haberdar olduğunuz sürece bunları makro olarak uygulayabilirsiniz.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Çoğu durumda, ne yapmaya çalıştığınızı belirlemek ve mümkün olan en iyi şekilde optimize etmek için derleyiciye bırakabilirsiniz. Bu gibi kullanıldığında sorunlara neden olsa da MAX(i++, j++)
, bir seferde maksimum artan değerlerin kontrol edilmesine ihtiyaç duyulduğundan şüpheliyim. Önce artırın, sonra kontrol edin.
Bu oldukça yakın bir gelişme nedeniyle geç bir cevaptır. OP, taşınabilir olmayan bir GCC (ve clang) uzantısına typeof
veya __typeof__
'temiz' ISO C'ye dayanan cevabı kabul ettiğinden , gcc-4.9'dan daha iyi bir çözüm var .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
Bu uzantının bariz yararı, __typeof__
çözümün aksine her makro argümanının yalnızca bir kez genişletilmesidir .
__auto_type
C ++ 11'lerin sınırlı bir formudur auto
. auto
C ++ 11 kullanıldığında üstün tip çıkarsama özelliklerini kullanmamak için iyi bir neden olmasa da, C ++ kodunda kullanılamaz (veya kullanılmamalıdır?) .
Bununla birlikte, makro bir kapsama dahil edildiğinde bu sözdizimini kullanan herhangi bir sorun olmadığını varsayıyorumextern "C" { ... }
; örneğin, bir C başlığından. AFAIK, bu uzantı kendi yolunu bulmadı clang
c-preprocessor
etiketi var. Gcc'nin __always_inline__
niteliği gibi bir şey kullanılmadığı sürece, bir işlevin adı geçen anahtar kelimeyle bile satır içinde olması garanti edilmez .
MSVC, GCC, C ve C ++ için çalışan bu sürümü yazdım .
#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
Pahalı bir daldan kaçınmak için min / max'a ihtiyacınız varsa, üçlü operatörü kullanmamalısınız, çünkü bir sıçramaya kadar derlenir. Aşağıdaki bağlantı dallanma olmadan bir min / maks fonksiyonunun uygulanması için kullanışlı bir yöntemi tarif etmektedir.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@ David Titarenco buradan çivilenmiş , ama güzel görünmesi ve her iki göstermeye temiz, en azından onu biraz beni let min()
ve max()
kopyalayarak ve burada daha kolay elde yapıştırarak yapmak için bir araya. :)
Güncelleme 25 Nisan 2020: Ayrıca, C ve C ++ öğrenenler veya birinden diğerine geçiş yapanlar için değerli bir karşılaştırma olarak, bunun C ++ şablonlarıyla nasıl yapılacağını göstermek için bir Bölüm 3 ekledim. Bu cevabı tekrar tekrar geri getirebileceğim kanonik bir referans yapmak için kapsamlı ve olgusal ve doğru olmak için elimden geleni yaptım ve umarım bunu benim kadar yararlı bulursunuz.
Bu teknik yaygın olarak kullanılır, düzgün bir şekilde nasıl kullanılacağını bilenler, bir şeyler yapmanın "de facto" yolu ve uygun şekilde kullanılırsa iyi kullanılır, ancak buggy (düşünün: çift değerlendirme yan etkisi ) karşılaştırmak için değişken atama dahil ifadeleri hiç geçmeyin :
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Bu teknik yukarıdaki "çift değerlendirme" yan etkilerini ve hatalarını önler ve bu nedenle bunu yapmanın üstün, daha güvenli ve "daha modern" GCC C yolu olarak kabul edilir. Hem gcc hem de clang derleyicileriyle çalışmasını bekleyin, çünkü clang tasarım gereği gcc uyumludur (bu cevabın altındaki clang notuna bakın).
AMA: " İfade ifadeleri açıkça satır içi olduğundan ve bu nedenle kendi yerel değişken kapsamına sahip OLMADIĞI değişken gölgeleme " etkilerine !
#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; \
})
Gcc deyimi ifadelerinde, kod bloğundaki son ifadenin , bir işlevden döndürülmüş gibi ifadeden "döndürülen" ifadesi olduğunu unutmayın.GCC'nin belgeleri bunu şöyle söylüyor:
Bileşik deyimindeki son şey bir ifade ve ardından noktalı virgül olmalıdır; bu alt ifadenin değeri tüm yapının değeri olarak işlev görür. (Parantez içinde başka tür bir ifade kullanırsanız, yapının türü geçersizdir ve dolayısıyla etkili bir şekilde değeri yoktur.)
C ++ Not: C ++ kullanılıyorsa, şablonlar muhtemelen bu tür yapı için önerilir, ancak şablonları kişisel olarak beğenmedim ve muhtemelen C ++ 'da yukarıdaki yapılardan birini kullanacağım, çünkü sık sık gömülü C ++' da C stillerini kullanıyorum ve tercih ediyorum.
Bu bölüm 25 Nisan 2020'yi ekledi:
Geçtiğimiz birkaç ay boyunca bir ton C ++ yapıyorum ve makroları şablonlar üzerinde tercih etme baskısı, mümkün olduğunda, C ++ topluluğunda oldukça güçlü. Sonuç olarak, şablonları kullanma konusunda daha iyi hale geldim ve C ++ şablon sürümlerini tamlık sağlamak ve bunu daha kanonik ve kapsamlı bir cevap haline getirmek istiyorum.
İşte temel işlev şablonu sürümleri max()
ve min()
C ++ gibi görünebilir:
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;
}
Burada C ++ şablonları hakkında ek okuma yapın: Wikipedia: Template (C ++) .
Ancak, her iki max()
ve min()
daha önce de C ++ standart bir kütüphanenin bir parçası olarak <algorithm>
başlığının ( #include <algorithm>
). C ++ standart kütüphanesinde, yukarıda gördüğümden biraz farklı tanımlanırlar. C ++ 14 için std::max<>()
ve std::min<>()
örneğin, yukarıdaki cplusplus.com bağlantılarındaki prototiplerine bakarak varsayılan prototipler şunlardır:
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);
Anahtar kelimenin o Not typename
için bir takma addır class
(kullanımları diyorsunuz göre denktir yüzden <typename T>
ya <class T>
sonradan C ++ şablonları icadından sonra kabul edildi beri şablon türü düzenli bir tip olabileceğini,) ( int
, float
vs.) yerine sadece sınıf türü.
Burada hem giriş türlerinin hem de dönüş türünün olduğunu görebilirsiniz const T&
, bu da "türe sürekli başvuru" anlamına gelir T
. Bu giriş parametreleri ve dönüş değeri anlamına gelir , referans ile geçirilen yerine değeri tarafından iletilir . Bu işaretçilerden geçmek gibidir ve sınıf nesneleri gibi büyük tipler için daha etkilidir. constexpr
İşlevinin bir parçası işlevlerinin değiştirir ve fonksiyon gösterir yeteneğine sahip olmalıdır eğer varsa, en az derleme zamanında (en değerlendirilenconstexpr
derleme zamanında değerlendirilen olamaz sonra varsayılan A geri, girdi parametreleri), ancak diğer normal fonksiyonlarda olduğu gibi çalışma zamanı değerlendirmesi.
Bir constexpr
C ++ işlevinin derleme zamanı özelliği, bir tür C-makro benzeri olmasını sağlar, çünkü bir constexpr
işlev için derleme zamanı değerlendirmesi mümkünse , derleme zamanında, bir MIN()
veyaMAX()
makro değiştirme belki olabilir derleme zamanında C veya C ++ 'da da tam olarak değerlendirilmelidir. Bu C ++ şablon bilgisi için ek başvurular için aşağıya bakın.
Wikipedia'dan Clang notu :
[Clang], derleme bayraklarının ve gayri resmi dil uzantılarının çoğunu destekleyen GNU Derleyici Koleksiyonu'nun (GCC) yerine geçecek şekilde tasarlanmıştır.
Bence dışarı Bu değer işaret var tanımladığınız eğer min
ve max
olarak üçüncül böyle ile
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
Daha sonra özel durum için aynı sonucu elde etmek için fmin(-0.0,0.0)
ve fmax(-0.0,0.0)
size argümanları takas gerekir
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
Görünüşe göre Windef.h
(la #include <windows.h>
) max
ve min
"küçük değerlendirme" zorluğundan muzdarip (küçük harf) makroları var, ancak kendi rollerini yeniden almak istemeyenler için oradalar :)
Adamýn "C" dediđini biliyorum ... Ama ţansýn varsa, bir C ++ şablonu kullan:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Güvenli yazın ve diğer yorumlarda belirtilen ++ ile sorun yok.
İki tam sayının maksimum a
ve b
olduğunu (int)(0.5((a+b)+abs(a-b)))
. Bu, çiftler için (double)
ve fabs(a-b)
çiftler için de kullanılabilir (şamandıralar için benzer)
En basit yol, onu bir .h
dosyada global bir işlev olarak tanımlamak ve programınız çok sayıda dosya ile modüler ise, istediğiniz zaman çağırmaktır. Değilse double MIN(a,b){return (a<b?a:b)}
, en basit yol.
warning: expression with side-effects multiply evaluated by macro
kullanım noktasında ...