Ở đâu MIN
và MAX
được định nghĩa trong C, nếu có?
Cách tốt nhất để thực hiện những điều này, một cách khái quát và gõ an toàn nhất có thể là gì? (Tiện ích mở rộng trình biên dịch / nội dung cho trình biên dịch chính được ưa thích.)
Ở đâu MIN
và MAX
được định nghĩa trong C, nếu có?
Cách tốt nhất để thực hiện những điều này, một cách khái quát và gõ an toàn nhất có thể là gì? (Tiện ích mở rộng trình biên dịch / nội dung cho trình biên dịch chính được ưa thích.)
Câu trả lời:
Ở đâu
MIN
vàMAX
được định nghĩa trong C, nếu có?
Họ không.
Cách tốt nhất để thực hiện những điều này là gì, nói chung và gõ an toàn nhất có thể (phần mở rộng / phần mềm biên dịch cho trình biên dịch chính được ưa thích).
Là chức năng. Tôi sẽ không sử dụng các macro như thế #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, đặc biệt nếu bạn dự định triển khai mã của mình. Hoặc tự viết, sử dụng một cái gì đó như tiêu chuẩn fmax
hoặc fmin
sửa lỗi macro bằng cách sử dụng loại của GCC (bạn cũng nhận được phần thưởng an toàn kiểu) trong biểu thức tuyên bố GCC :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Mọi người đều nói "ồ tôi biết về đánh giá kép, không vấn đề gì" và vài tháng sau, bạn sẽ gỡ lỗi cho những vấn đề khó hiểu nhất trong nhiều giờ liền.
Lưu ý việc sử dụng __typeof__
thay vì typeof
:
Nếu bạn đang viết một tệp tiêu đề phải hoạt động khi được bao gồm trong các chương trình ISO C, hãy viết
__typeof__
thay vìtypeof
.
decltype
từ khóa mới MSVC ++ 2010 - nhưng ngay cả như vậy, Visual Studio không thể thực hiện các câu lệnh ghép trong macro (và decltype
dù sao cũng là C ++), tức là ({ ... })
cú pháp của GCC nên tôi chắc chắn rằng điều đó là không thể. Tôi đã không xem xét bất kỳ trình biên dịch nào khác liên quan đến vấn đề này, xin lỗi Luther: S
MAX(someUpperBound, someRandomFunction())
để giới hạn một giá trị ngẫu nhiên ở một số giới hạn trên. Đó là một ý tưởng tồi tệ, nhưng nó thậm chí còn không hiệu quả, bởi vì MAX
anh ta đang sử dụng có vấn đề đánh giá kép, vì vậy anh ta kết thúc với một số ngẫu nhiên khác với số được đánh giá ban đầu.
MIN(x++, y++)
bộ tiền xử lý sẽ tạo mã sau (((x++) < (y++)) ? (x++) : (y++))
. Vì vậy, x
vày
sẽ được tăng lên hai lần.
Nó cũng được cung cấp trong các phiên bản GNU libc (Linux) và FreeBSD của sys / param.h và có định nghĩa được cung cấp bởi dreamlax.
Trên 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.
Trên 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))
Các kho lưu trữ nguồn ở đây:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
quá. :) Thật không thể di động.
Có một std::min
và std::max
trong C ++, nhưng AFAIK, không có thư viện chuẩn C nào tương đương. Bạn có thể tự xác định chúng bằng các macro như
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Nhưng điều này gây ra vấn đề nếu bạn viết một cái gì đó như MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
không phải là một cách linh hoạt, tại sao ???
#define MULT(x, y) x * y
. Sau đó MULT(a + b, a + b)
mở rộng đến a + b * a + b
, phân tích cú pháp a + (b * a) + b
do ưu tiên. Đó không phải là những gì lập trình viên có thể dự định.
Tránh các phần mở rộng trình biên dịch không chuẩn và triển khai nó dưới dạng macro hoàn toàn an toàn loại trong tiêu chuẩn thuần C (ISO 9899: 2011).
Giải pháp
#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))
Sử dụng
MAX(int, 2, 3)
Giải trình
Macro MAX tạo một macro khác dựa trên type
tham số. Macro điều khiển này, nếu được triển khai cho loại đã cho, được sử dụng để kiểm tra cả hai tham số có đúng loại không. Nếutype
không được hỗ trợ, sẽ có lỗi trình biên dịch.
Nếu x hoặc y không đúng loại, sẽ có lỗi trình biên dịch trong ENSURE_
macro. Nhiều macro như vậy có thể được thêm vào nếu nhiều loại được hỗ trợ. Tôi đã giả định rằng chỉ các loại số học (số nguyên, số float, con trỏ, v.v.) sẽ được sử dụng và không sử dụng cấu trúc hoặc mảng, v.v.
Nếu tất cả các loại đều đúng, macro GENERIC_MAX sẽ được gọi. Cần có dấu ngoặc đơn xung quanh mỗi tham số macro, như biện pháp phòng ngừa tiêu chuẩn thông thường khi viết macro C.
Sau đó, có các vấn đề thông thường với các chương trình khuyến mãi ngầm định trong C. Nhà ?:
điều hành cân bằng toán hạng thứ 2 và thứ 3 với nhau. Ví dụ: kết quả của GENERIC_MAX(my_char1, my_char2)
sẽ là mộtint
. Để ngăn macro thực hiện các quảng cáo loại nguy hiểm tiềm tàng như vậy, loại cuối cùng được sử dụng cho loại dự định đã được sử dụng.
Cơ sở lý luận
Chúng tôi muốn cả hai tham số cho macro là cùng loại. Nếu một trong số chúng thuộc loại khác, macro không còn là loại an toàn, bởi vì một toán tử thích?:
sẽ mang lại các chương trình khuyến mãi kiểu ngầm. Và bởi vì nó, chúng ta cũng luôn cần đưa kết quả cuối cùng trở lại loại dự định như đã giải thích ở trên.
Một macro chỉ với một tham số có thể đã được viết theo cách đơn giản hơn nhiều. Nhưng với 2 tham số trở lên, cần bao gồm một tham số loại phụ. Bởi vì điều này thật không may là không thể:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Vấn đề là nếu macro ở trên được gọi là MAX(1, 2)
với hai int
, nó vẫn sẽ cố gắng mở rộng macro tất cả các kịch bản có thể có của _Generic
danh sách liên kết. Vì vậy, ENSURE_float
macro cũng sẽ được mở rộng, mặc dù nó không liên quan int
. Và vì macro cố ý chỉ chứa float
loại, mã sẽ không được biên dịch.
Để giải quyết điều này, tôi đã tạo tên macro trong giai đoạn tiền xử lý thay vào đó, với toán tử ##, để không có macro nào vô tình được mở rộng.
Ví dụ
#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
macro đó là một ý tưởng tồi, bạn chỉ phải cố gắng GENERIC_MAX(var++, 7)
tìm hiểu lý do tại sao :-) Ngày nay (đặc biệt là với trình biên dịch tối ưu hóa / nội tuyến rất nhiều), các macro chỉ nên được chuyển sang các dạng đơn giản. Các hàm giống như hàm tốt hơn là hàm và nhóm giá trị tốt hơn khi liệt kê.
Tôi không nghĩ rằng chúng là các macro được tiêu chuẩn hóa. Có các chức năng được tiêu chuẩn hóa cho điểm nổi đã có, fmax
và fmin
(và fmaxf
cho phao và fmaxl
cho đôi dài).
Bạn có thể triển khai chúng dưới dạng macro miễn là bạn nhận thức được các vấn đề về tác dụng phụ / đánh giá kép.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Trong hầu hết các trường hợp, bạn có thể để nó cho trình biên dịch để xác định những gì bạn đang cố gắng làm và tối ưu hóa nó tốt nhất có thể. Mặc dù điều này gây ra vấn đề khi được sử dụng như thế nào MAX(i++, j++)
, tôi nghi ngờ sẽ có rất nhiều nhu cầu trong việc kiểm tra tối đa các giá trị gia tăng trong một lần. Tăng trước, sau đó kiểm tra.
Đây là một câu trả lời muộn, do một sự phát triển khá gần đây. Vì OP đã chấp nhận câu trả lời dựa trên phần mở rộng GCC (và clang) không di động typeof
- hoặc __typeof__
cho 'C' sạch '- nên có một giải pháp tốt hơn có sẵn từ gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
Lợi ích rõ ràng của phần mở rộng này là mỗi đối số macro chỉ được mở rộng một lần, không giống như __typeof__
giải pháp.
__auto_type
là một dạng hạn chế của C ++ 11's auto
. Nó không thể (hoặc không nên?) Được sử dụng trong mã C ++, mặc dù không có lý do chính đáng nào để không sử dụng các khả năng suy luận loại ưu việt củaauto
khi sử dụng C ++ 11.
Điều đó nói rằng, tôi cho rằng không có vấn đề gì khi sử dụng cú pháp này khi macro được bao gồm trong một extern "C" { ... }
phạm vi; ví dụ: từ một tiêu đề C. AFAIK, tiện ích mở rộng này chưa tìm thấy thông tin.
clang
bắt đầu hỗ trợ __auto_type
vào khoảng năm 2016 (xem bản vá ).
c-preprocessor
thẻ. Một chức năng không được đảm bảo để được nội tuyến ngay cả với từ khóa đã nói, trừ khi sử dụng một cái gì đó như __always_inline__
thuộc tính của gcc .
Tôi đã viết phiên bản này hoạt động cho MSVC, GCC, C và 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
Nếu bạn cần tối thiểu / tối đa để tránh một nhánh đắt tiền, bạn không nên sử dụng toán tử ternary, vì nó sẽ biên dịch thành một bước nhảy. Liên kết dưới đây mô tả một phương pháp hữu ích để thực hiện chức năng tối thiểu / tối đa mà không cần phân nhánh.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco đóng đinh nó ở đây , nhưng ít nhất hãy để tôi dọn dẹp nó một chút để làm cho nó trông đẹp hơn, và hiển thị cả hai min()
và max()
cùng nhau để sao chép và dán từ đây dễ dàng hơn. :)
Cập nhật ngày 25 tháng 4 năm 2020: Tôi cũng đã thêm Phần 3 để chỉ ra cách thực hiện với các mẫu C ++, như một so sánh có giá trị cho những người học cả C và C ++, hoặc chuyển từ loại này sang loại khác. Tôi đã làm hết sức mình để thấu đáo và thực tế và chính xác để biến câu trả lời này thành một tài liệu tham khảo kinh điển mà tôi có thể quay lại nhiều lần và tôi hy vọng bạn thấy nó hữu ích như tôi.
Kỹ thuật này được sử dụng phổ biến, được tôn trọng bởi những người biết sử dụng nó đúng cách, cách làm "thực tế" và sử dụng tốt nếu sử dụng đúng cách, nhưng lỗi (nghĩ: tác dụng phụ đánh giá kép ) nếu bạn bao giờ vượt qua các biểu thức bao gồm cả gán biến để so sánh:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Kỹ thuật này tránh được các tác dụng phụ và lỗi "đánh giá kép" ở trên, và do đó được coi là cách GCC C vượt trội, an toàn hơn và "hiện đại hơn" để thực hiện điều này. Mong đợi nó hoạt động với cả trình biên dịch gcc và clang, vì clang, theo thiết kế, tương thích với gcc (xem ghi chú tiếng kêu ở cuối câu trả lời này).
NHƯNG: Đừng coi chừng các hiệu ứng " bóng biến " vẫn còn, vì các biểu thức câu lệnh rõ ràng được nội tuyến và do đó KHÔNG có phạm vi biến cục bộ của riêng chúng!
#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; \
})
Lưu ý rằng trong các biểu thức câu lệnh gcc, biểu thức cuối cùng trong khối mã là những gì được "trả về" từ biểu thức, như thể nó được trả về từ một hàm. Tài liệu của GCC nói theo cách này:
Điều cuối cùng trong câu lệnh ghép phải là một biểu thức theo sau dấu chấm phẩy; giá trị của biểu hiện phụ này đóng vai trò là giá trị của toàn bộ cấu trúc. (Nếu bạn sử dụng một số loại câu lệnh khác tồn tại trong dấu ngoặc nhọn, cấu trúc có kiểu void và do đó không có giá trị.)
C ++ Lưu ý: nếu sử dụng C ++, các mẫu có thể được khuyến nghị cho loại cấu trúc này, nhưng cá nhân tôi không thích các mẫu và có thể sẽ sử dụng một trong các cấu trúc trên trong C ++, vì tôi cũng thường xuyên sử dụng và thích các kiểu C trong nhúng C ++.
Phần này được thêm vào ngày 25 tháng 4 năm 2020:
Tôi đã làm rất nhiều C ++ trong vài tháng qua và áp lực phải thích các mẫu hơn các macro, trong đó có thể, trong cộng đồng C ++ khá mạnh. Kết quả là, tôi đã trở nên tốt hơn trong việc sử dụng các mẫu và muốn đưa vào các phiên bản mẫu C ++ ở đây để hoàn thiện và để làm cho câu trả lời chính xác và kỹ lưỡng hơn này.
Đây là phiên bản mẫu chức năng cơ bản của max()
và min()
có thể trông giống như trong 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;
}
Đọc thêm về các mẫu C ++ tại đây: Wikipedia: Mẫu (C ++) .
Tuy nhiên, cả hai max()
và min()
đã là một phần của thư viện chuẩn C ++, trong <algorithm>
tiêu đề ( #include <algorithm>
). Trong thư viện chuẩn C ++, chúng được định nghĩa hơi khác so với tôi có ở trên. Các nguyên mẫu mặc định cho std::max<>()
và std::min<>()
, ví dụ, trong C ++ 14, xem xét các nguyên mẫu của chúng trong các liên kết cplusplus.com ngay phía trên, là:
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);
Lưu ý rằng các từ khóa typename
là một bí danh để class
(vì vậy việc sử dụng của họ là giống hệt nhau cho dù bạn nói <typename T>
hay <class T>
), vì nó sau đó đã được thừa nhận sau khi phát minh ra C mẫu ++, rằng kiểu mẫu có thể là một loại thường ( int
, float
, vv) thay vì chỉ một loại lớp.
Ở đây bạn có thể thấy rằng cả hai loại đầu vào, cũng như loại trả về const T&
, có nghĩa là "tham chiếu liên tục đến loại T
". Điều này có nghĩa là các tham số đầu vào và giá trị trả về được truyền bằng tham chiếu thay vì truyền qua giá trị . Điều này giống như chuyển qua các con trỏ và hiệu quả hơn đối với các loại lớn, chẳng hạn như các đối tượng lớp. Phần constexpr
của hàm tự sửa đổi hàm và chỉ ra rằng hàm phải có khả năng được đánh giá tại thời gian biên dịch (ít nhất là nếu được cung cấpconstexpr
các tham số đầu vào), nhưng nếu nó không thể được đánh giá tại thời gian biên dịch, thì nó sẽ mặc định trở lại đánh giá thời gian chạy, giống như bất kỳ chức năng bình thường khác.
Khía cạnh thời gian biên dịch của hàm constexpr
C ++ làm cho nó giống kiểu C-macro, trong đó nếu đánh giá thời gian biên dịch có thể cho một constexpr
hàm, thì nó sẽ được thực hiện tại thời gian biên dịch, giống như một MIN()
hoặcMAX()
thay vĩ mô có thể có thể cũng được đánh giá đầy đủ tại thời gian biên dịch trong C hoặc C ++. Để tham khảo thêm cho thông tin mẫu C ++ này, xem bên dưới.
Clang lưu ý từ Wikipedia :
[Clang] được thiết kế để hoạt động như một sự thay thế thả xuống cho Bộ sưu tập trình biên dịch GNU (GCC), hỗ trợ hầu hết các cờ biên dịch và các phần mở rộng ngôn ngữ không chính thức.
Thật đáng để chỉ ra Tôi nghĩ rằng nếu bạn xác định min
và max
với đại học như
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
sau đó để có được kết quả tương tự cho trường hợp đặc biệt fmin(-0.0,0.0)
và fmax(-0.0,0.0)
bạn cần trao đổi các đối số
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
Có vẻ như Windef.h
(a la #include <windows.h>
) có max
và min
(chữ thường) macro, cũng gặp phải khó khăn "đánh giá kép", nhưng chúng ở đó cho những người không muốn tự cuộn lại :)
Tôi biết anh chàng nói "C" ... Nhưng nếu bạn có cơ hội, hãy sử dụng mẫu C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Nhập an toàn và không có vấn đề với ++ được đề cập trong các bình luận khác.
Tối đa của hai số nguyên a
và b
là (int)(0.5((a+b)+abs(a-b)))
. Điều này cũng có thể làm việc với (double)
và fabs(a-b)
cho đôi (tương tự cho phao)
Cách đơn giản nhất là định nghĩa nó là một hàm toàn cục trong một .h
tệp và gọi nó bất cứ khi nào bạn muốn, nếu chương trình của bạn là mô-đun với nhiều tệp. Nếu không, double MIN(a,b){return (a<b?a:b)}
là cách đơn giản nhất.
warning: expression with side-effects multiply evaluated by macro
tại điểm sử dụng ...