Làm thế nào để tôi im lặng tốt nhất một cảnh báo về các biến không sử dụng?


236

Tôi có một ứng dụng đa nền tảng và trong một vài chức năng của tôi, không phải tất cả các giá trị được truyền cho các hàm đều được sử dụng. Do đó tôi nhận được cảnh báo từ GCC nói với tôi rằng có các biến không được sử dụng.

Điều gì sẽ là cách tốt nhất của mã hóa xung quanh cảnh báo?

Một #ifdef xung quanh chức năng?

#ifdef _MSC_VER
void ProcessOps::sendToExternalApp(QString sAppName, QString sImagePath, qreal qrLeft, qreal qrTop, qreal qrWidth, qreal qrHeight)
#else
void ProcessOps::sendToExternalApp(QString sAppName, QString sImagePath, qreal /*qrLeft*/, qreal /*qrTop*/, qreal /*qrWidth*/, qreal /*qrHeight*/)
#endif
{

Điều này là rất xấu nhưng có vẻ như cách trình biên dịch sẽ thích.

Hay tôi gán 0 cho biến ở cuối hàm? (điều mà tôi ghét vì nó làm thay đổi thứ gì đó trong luồng chương trình để tắt tiếng cảnh báo trình biên dịch).

Có một cách chính xác?


7
Tôi chỉ nhận ra bạn đã hỏi một câu hỏi tương tự vào tháng 11 năm ngoái. Đây là lý do tại sao nó trông quen thuộc! ;) stackoverflow.com/questions/308277/ Mạnh
Alex B

9
Tại sao không chỉ nhận xét chúng cho cả hai trình biên dịch? Nếu đối số không được sử dụng trên một, thì có lẽ nó sẽ không được sử dụng ở bên kia ...
Roger Lipscombe

12
bạn nên biết rằng Qt có một Q_UNUSEDmacro chỉ cho việc này. Kiểm tra nó trong tài liệu.
Evan Teran

1
Giải pháp C cũng hoạt động tốt trong C ++: stackoverflow.com/a35399170/1904815
JonnyJD

-Wno-không sử dụng tham số cũng có thể là một tùy chọn nếu bạn có thể có các cờ xây dựng dành riêng cho trình biên dịch
Mã Abominator

Câu trả lời:


326

Bạn có thể đặt nó trong (void)var;biểu thức "" (không làm gì) để trình biên dịch thấy nó được sử dụng. Đây là di động giữa các trình biên dịch.

Ví dụ

void foo(int param1, int param2)
{
    (void)param2;
    bar(param1);
}

Hoặc là,

#define UNUSED(expr) do { (void)(expr); } while (0)
...

void foo(int param1, int param2)
{
    UNUSED(param2);
    bar(param1);
}

22
+1 - tôi vẫn sẽ ghi lại lý do tại sao bạn không sử dụng biến ngay cả khi nó ở đó.
Tobias Langner

18
Đây là cách Q_UNUSEDthực hiện trên nguyên tắc.
Dmitry Volosnykh

11
@Cameron bạn chỉ có thể bỏ qua tên tham số trong C ++. Nếu nó được tạo khuôn mẫu, nó sẽ không được sử dụng trong C, vì vậy bạn không cần thủ thuật cast-to-void.
Alex B

13
Chỉ #define UNUSED(expr) (void)(expr)nên làm việc quá (không có do-while).
JonnyJD

7
Tôi tự hỏi làm thế nào để làm điều đó cho một mẫu matrixdic. Trong template<typename... Args> void f(const Args&... args)tôi không thể viết (void)args;hoặc (void)args...;vì cả hai đều là lỗi cú pháp.
panzi

101

Trong GCC và Clang, bạn có thể sử dụng __attribute__((unused))chỉ thị tiền xử lý để đạt được mục tiêu của mình.
Ví dụ:

int foo (__attribute__((unused)) int bar) {
   return 0;
}

1
Đây là giải pháp tốt nhất cho các chức năng gọi lại.
Sonic Atom

1
Cũng được hỗ trợ bởi clang
Alexander


39

Giải pháp hiện tại của bạn là tốt nhất - nhận xét tên tham số nếu bạn không sử dụng nó. Điều đó áp dụng cho tất cả các trình biên dịch, vì vậy bạn không phải sử dụng bộ xử lý trước để làm điều đó đặc biệt cho GCC.


7
Chỉ để củng cố câu trả lời này - bạn không cần #ifdef, chỉ cần nhận xét các tên tham số không sử dụng.
quamrana

4
Tôi có một trường hợp trong đó tham số là một phần của một cuộc gọi lại và nhận xét nó phá vỡ trình biên dịch (vì vậy tôi không chắc tại sao lại g++cảnh báo về nó.) Trong trường hợp như vậy, bạn muốn giới thiệu gì?
Drew Noakes

1
Hãy tưởng tượng một phương thức ảo nội tuyến với các tham số không sử dụng / * nhận xét * /, ứng dụng khách của giao diện sẽ không thấy tên tham số trong quá trình tự động hoàn thành trong hầu hết các IDE. Trong trường hợp này, giải pháp UNUSED () thuận tiện hơn, mặc dù ít sạch hơn.
cbuchart

Tôi nghĩ đơn giản hơn là tốt hơn, bình luận rất rõ ràng
fievel 16/11/18

26

Cập nhật C ++ 17

Trong C ++ 17, chúng ta có được thuộc tính [[might_unuse]] được bao phủ trong [dcl.attr.unuse]

Mã thông báo thuộc tính có thể_unuse chỉ ra rằng tên hoặc thực thể có thể không được sử dụng có chủ ý. Nó sẽ xuất hiện nhiều nhất một lần trong mỗi danh sách thuộc tính và không có mệnh đề đối số thuộc tính nào xuất hiện. ...

Thí dụ:

 [[maybe_unused]] void f([[maybe_unused]] bool thing1,
                        [[maybe_unused]] bool thing2) {
  [[maybe_unused]] bool b = thing1 && thing2;
    assert(b);
 }

Việc triển khai không nên cảnh báo rằng b không được sử dụng, cho dù NDEBUG có được xác định hay không. Ví dụ

Ví dụ sau:

int foo ( int bar) {
    bool unused_bool ;
    return 0;
}

Cả clang và gcc đều tạo ra chẩn đoán bằng cách sử dụng -Wall -Wextra cho cả thanhun used_bool ( Xem trực tiếp ).

Trong khi thêm [[may_unuse]] làm im lặng chẩn đoán:

int foo ([[maybe_unused]] int bar) {
    [[maybe_unused]] bool unused_bool ;
    return 0;
}

nhìn thấy nó sống .

Trước C ++ 17

Trong C ++ 11, một dạng thay thế của UNUSEDmacro có thể được hình thành bằng cách sử dụng biểu thức lambda ( thông qua Ben Deane ) với một bản ghi của biến không được sử dụng:

#define UNUSED(x) [&x]{}()

Yêu cầu ngay lập tức của biểu thức lambda nên được tối ưu hóa, đưa ra ví dụ sau:

int foo (int bar) {
    UNUSED(bar) ;
    return 0;
}

chúng ta có thể thấy trong godbolt rằng cuộc gọi được tối ưu hóa đi:

foo(int):
xorl    %eax, %eax
ret

5
Vì vậy, bạn đề cập đến C ++ 11 và sau đó quản lý để trình bày một macro?! Ôi! Có lẽ sử dụng một chức năng sẽ được sạch hơn? template <class T> inline void NOTUSED( T const & result ) { static_cast<void>(result); }Bạn cũng có thể sử dụng lambda trong hàm, tôi cho là vậy.
Alexis Wilke

godbolt là một nguồn tài nguyên tuyệt vời
yano

5
[&x]{}()không thực sự im lặng cảnh báo, nhưng thay vào đó chuyển cảnh báo từ chức năng người gọi sang lambda. Sẽ mất thời gian cho đến khi trình biên dịch xác định đây là một cảnh báo, nhưng clang-tidy đã phàn nàn về một biến không được sử dụng trong danh sách chụp.
nVxx

25

Một cách thậm chí còn sạch hơn là chỉ nhận xét tên biến:

int main(int /* argc */, char const** /* argv */) {
  return 0;
}

8
Điều này là không tốt nếu bạn có doxygen và muốn ghi lại các tham số.
Alexis Wilke

18
@AlexisWilke: Điều đó sẽ đủ điều kiện là một lỗi trong doxygen, IMO
6502

3
Bạn có thể #define YOU_PRO DỰ_UNUSED (argname) một cách có điều kiện dựa trên #ifdef DOXYGEN để doxygen có thể thấy tên và trình biên dịch thực không, thông qua int main (int YOU_PRO DỰ_UNUSED (argc), ...). Không tuyệt vời, nhưng làm việc.
mabraham

Tôi thấy rất đau đớn khi bình luận ra một khối mã với nhiều bình luận lồng nhau như vậy. (trình biên dịch phàn nàn về mọi người).
Jeff McClintock

@JeffMcClintock chỉ sử dụng nhận xét một dòng. Hầu hết các trình soạn thảo phong nha đều hỗ trợ chỉnh sửa khối dọc (ví dụ [Ctrl] + [V] trong Vim). Nếu không, sử dụng #if 0 / #endifbình luận khối.
Ruslan

24

Một đồng nghiệp chỉ cho tôi đến macro nhỏ tốt đẹp này ở đây

Để dễ dàng tôi sẽ bao gồm các macro bên dưới.

#ifdef UNUSED
#elif defined(__GNUC__) 
# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) 
#elif defined(__LCLINT__) 
# define UNUSED(x) /*@unused@*/ x 
#else 
# define UNUSED(x) x 
#endif

void dcc_mon_siginfo_handler(int UNUSED(whatsig))

12
"đẹp" "macro" "c ++" - chọn 2.
Jeff McClintock

23

không gắn cờ các cảnh báo này theo mặc định. Cảnh báo này phải được bật một cách rõ ràng bằng cách chuyển -Wunused-parameterđến trình biên dịch hoặc ngầm định bằng cách chuyển -Wall -Wextra(hoặc có thể một số tổ hợp cờ khác).

Các cảnh báo tham số không được sử dụng đơn giản có thể được loại bỏ bằng cách chuyển -Wno-unused-parameterđến trình biên dịch, nhưng lưu ý rằng cờ vô hiệu hóa này phải xuất hiện sau bất kỳ cờ kích hoạt nào có thể cho cảnh báo này trong dòng lệnh của trình biên dịch, để nó có thể có hiệu lực.


2
Mặc dù, đây có thể không phải là câu trả lời tốt nhất cho câu hỏi (vì câu hỏi là làm thế nào để tránh cảnh báo, không phải cách tắt nó), câu trả lời này có thể là những người đến từ google (như tôi) đang tìm kiếm ("làm thế nào để vô hiệu hóa cảnh báo này "). Vì vậy, tôi cho +1, cảm ơn câu trả lời của bạn!
mozzbozz

13

cách ít macro và di động để khai báo một hoặc nhiều tham số là không sử dụng:

template <typename... Args> inline void unused(Args&&...) {}

int main(int argc, char* argv[])
{
    unused(argc, argv);
    return 0;
}

Rất tốt, nhưng lưu ý rằng điều này đòi hỏi C ++ 11 (hoặc mới hơn, tất nhiên).
Paul R

Tôi đã bỏ phiếu cho câu trả lời này vì tôi không muốn hy sinh thời gian biên dịch (bằng cách sử dụng các mẫu) chỉ để thoát khỏi cảnh báo.
Konrad Kleine

@KonradKleine: Điều này có thể tiêu tốn bao nhiêu thời gian biên dịch? Thử nghiệm trên máy tính của tôi, tôi có thể thực hiện hàng ngàn cuộc gọi () chưa sử dụng này trong một phần mười giây.
Daniel McLaury

@DanielMcLaury đây chỉ là phỏng đoán của tôi và tôi chưa thực hiện bất kỳ thử nghiệm nào.
Konrad Kleine

8

Sử dụng các chỉ thị tiền xử lý được coi là xấu xa hầu hết thời gian. Lý tưởng nhất là bạn muốn tránh chúng như Pest. Hãy nhớ rằng làm cho trình biên dịch hiểu mã của bạn là dễ dàng, cho phép các lập trình viên khác hiểu mã của bạn khó hơn nhiều. Một vài chục trường hợp như thế này ở đây và ở đó làm cho nó rất khó để đọc cho chính bạn sau này hoặc cho người khác ngay bây giờ.

Một cách có thể là đặt các tham số của bạn lại với nhau thành một loại lớp đối số. Sau đó, bạn chỉ có thể sử dụng một tập hợp con của các biến (tương đương với việc gán 0 thực sự của bạn) hoặc có các chuyên môn khác nhau của lớp đối số đó cho mỗi nền tảng. Điều này tuy nhiên có thể không đáng, bạn cần phân tích xem nó có phù hợp không.

Nếu bạn có thể đọc các mẫu không thể, bạn có thể tìm thấy các mẹo nâng cao trong sách "Đặc biệt C ++". Nếu những người đọc mã của bạn có thể có được bộ kỹ năng của họ để bao gồm những thứ điên rồ được dạy trong cuốn sách đó, thì bạn sẽ có mã đẹp mà cũng có thể dễ dàng đọc được. Trình biên dịch cũng sẽ nhận thức rõ về những gì bạn đang làm (thay vì ẩn mọi thứ bằng cách tiền xử lý)


5
"Sử dụng các chỉ thị tiền xử lý được coi là xấu xa hầu hết thời gian." Có thật không? Bởi ai?
Graeme Perrow

12
Bởi bất cứ ai quan tâm đến phạm vi, có thể gỡ lỗi đúng cách, hoặc sự tỉnh táo của họ.
Hóa đơn

2
@Graeme, có vẻ vô tội khi chúng ta chỉ nhìn thấy 4 dòng của nó, nhưng lan ra xung quanh nó gây đau đầu. #ifdef về cơ bản cho phép bạn đặt nhiều phiên bản của một mã nguồn mà trình biên dịch sẽ chỉ nhìn thấy một phiên bản. Như Bill đề cập, nó cũng làm cho việc gỡ lỗi khó khăn hơn. Tôi đã đọc về sự xấu xa của các chỉ thị tiền xử lý trong các cuốn sách và blog đa dạng, cũng như tự mình trải nghiệm nó. Tất nhiên, mọi thứ đều tương đối. Đôi khi các chỉ thị tiền xử lý chỉ đơn giản là có ý nghĩa bởi vì bất cứ điều gì khác sẽ có hậu quả tồi tệ hơn, và quan điểm của tôi chỉ ở đây là nên tránh khi có thể.
Ben Dadsetan

1
Sử dụng quá mức là xấu, nhưng tôi sẽ gọi là #define UNUSED(expr) (void)(expr)thích hợp.
JonnyJD

7

Trước hết cảnh báo được tạo bởi định nghĩa biến trong tệp nguồn chứ không phải tệp tiêu đề. Tiêu đề có thể nguyên sơ và nên, vì bạn có thể đang sử dụng một cái gì đó như doxygen để tạo tài liệu API.

Tôi sẽ giả định rằng bạn có triển khai hoàn toàn khác nhau trong các tệp nguồn. Trong những trường hợp này, bạn có thể nhận xét tham số vi phạm hoặc chỉ viết tham số.

Thí dụ:

func(int a, int b)
{
    b;
    foo(a);
}

Điều này có vẻ khó hiểu, do đó, đã xác định một macro như UNUSED. Cách mà MFC đã làm là:

#ifdef _DEBUG
#define UNUSED(x)
#else
#define UNUSED(x) x
#endif

Như thế này bạn thấy cảnh báo vẫn còn trong các bản dựng gỡ lỗi, có thể hữu ích.


4

Nó không an toàn để luôn luôn nhận xét tên tham số? Nếu không, bạn có thể làm một cái gì đó như

#ifdef _MSC_VER
# define P_(n) n
#else
# define P_(n)
#endif

void ProcessOps::sendToExternalApp(
    QString sAppName, QString sImagePath,
    qreal P_(qrLeft), qreal P_(qrTop), qreal P_(qrWidth), qreal P_(qrHeight))

hơi xấu xí một chút .


4
Thực tế là tên param không bắt buộc trong C ++ - nó có trong C - chỉ là để đưa ra một cách tiêu chuẩn và dễ dàng để ngăn chặn cảnh báo.
AProgrammer

1
@hacker, chưa bao giờ nói thế. Tôi có xu hướng chỉ ra sự khác biệt giữa C và C ++, đặc biệt là khi chúng ở trong các khu vực mà bạn nghĩ là tập hợp con phổ biến ... Chỉ là một thói quen vì tôi đang làm việc trên cơ sở mã hỗn hợp.
Lập trình viên

3

Sử dụng một UNREFERENCED_PARAMETER(p) có thể làm việc. Tôi biết nó được định nghĩa trong WinNT.h cho các hệ thống Windows và cũng có thể dễ dàng được định nghĩa cho gcc (nếu nó chưa có nó).

UNREFERENCED PARAMETER(p) được định nghĩa là

#define UNREFERENCED_PARAMETER(P)          (P)

trong WinNT.h.


3

Tôi đã thấy điều này thay vì (void)param2cách im lặng cảnh báo:

void foo(int param1, int param2)
{
    std::ignore = param2;
    bar(param1);
}

Có vẻ như điều này đã được thêm vào trong C ++ 11


Có vẻ như làm một cái gì đó, không bị bỏ qua sau khi biên dịch.
GyuHyeon Choi

2

Sử dụng cờ của trình biên dịch, ví dụ cờ cho GCC: -Wno-unused-variable


1

Bạn có thể sử dụng __unusedđể báo cho trình biên dịch rằng biến có thể không được sử dụng.

- (void)myMethod:(__unused NSObject *)theObject    
{
    // there will be no warning about `theObject`, because you wrote `__unused`

    __unused int theInt = 0;
    // there will be no warning, but you are still able to use `theInt` in the future
}

2
trình biên dịch? Bởi vì __unusedkhông phải là C ++ tiêu chuẩn, và hơn thế nữa, đó cũng không phải là những gì bạn đã đăng ... Đó là Mục tiêu-C. Vì vậy, câu trả lời này chỉ thực sự hữu ích cho (các) trình biên dịch cụ thể và nó làm cho mã không khả chuyển, và trên thực tế không thực sự hợp lệ vì mã người dùng không có nghĩa là sử dụng mã định danh bắt đầu __, được dành riêng cho việc triển khai.
gạch dưới

1

Trong C ++ 11, đây là giải pháp tôi đang sử dụng:

template<typename... Ts> inline void Unreferenced(Ts&&...) {}

int Foo(int bar) 
{
    Unreferenced(bar);
    return 0;
}

int Foo2(int bar1, int bar2) 
{
    Unreferenced(bar1, bar2);
    return 0;
}

Được xác minh là có thể mang theo (ít nhất là trên msvc, clang và gcc hiện đại) và không tạo thêm mã khi tối ưu hóa được bật. Không có tối ưu hóa, cuộc gọi chức năng bổ sung được thực hiện và các tham chiếu đến các tham số được sao chép vào ngăn xếp, nhưng không có macro liên quan.

Nếu mã bổ sung là một vấn đề, bạn có thể sử dụng khai báo này thay thế:

(decltype(Unreferenced(bar1, bar2)))0;

nhưng tại thời điểm đó, một macro cung cấp khả năng đọc tốt hơn:

#define UNREFERENCED(...) { (decltype(Unreferenced(__VA_ARGS__)))0; }

1

Điều này hoạt động tốt nhưng yêu cầu C ++ 11

template <typename ...Args>
void unused(Args&& ...args)
{
  (void)(sizeof...(args));
}

1
Điều gì về điều này đòi hỏi C ++ 14 và sẽ không hoạt động trong C ++ 11? Tôi không thể nhìn thấy bất cứ điều gì. Ngoài ra, nó không được khuyến khích sử dụng ALLCAPScho bất cứ thứ gì ngoại trừ macro, điều này làm cho chúng trông xấu xí và không mong muốn, nhưng thực sự không có gì xấu về điều này, thực sự, ngoại trừ việc static_castsẽ đẹp hơn.
gạch dưới

0

Tôi thấy hầu hết các câu trả lời được trình bày chỉ hoạt động cho biến không sử dụng cục bộ và sẽ gây ra lỗi biên dịch cho biến toàn cục tĩnh không sử dụng.

Một macro khác cần thiết để ngăn chặn cảnh báo của biến toàn cục tĩnh không sử dụng.

template <typename T>
const T* UNUSED_VARIABLE(const T& dummy) { 
    return &dummy;
}
#define UNUSED_GLOBAL_VARIABLE(x) namespace {\
    const auto dummy = UNUSED_VARIABLE(x);\
}

static int a = 0;
UNUSED_GLOBAL_VARIABLE(a);

int main ()
{
    int b = 3;
    UNUSED_VARIABLE(b);
    return 0;
}

Điều này hoạt động vì không có cảnh báo nào được báo cáo cho biến toàn cục không tĩnh trong không gian tên ẩn danh.

C ++ 11 là bắt buộc

 g++  -Wall -O3  -std=c++11 test.cpp

0

Cười lớn! Tôi không nghĩ rằng có một câu hỏi khác về SO tiết lộ tất cả những kẻ dị giáo bị hỏng bởi Chaos tốt hơn câu hỏi này!

Với tất cả sự tôn trọng đối với C ++ 17, có một hướng dẫn rõ ràng trong Nguyên tắc cốt lõi của C ++ . AFAIR, trở lại vào năm 2009, tùy chọn này đã có sẵn cũng như ngày hôm nay. Và nếu ai đó nói rằng nó được coi là một lỗi trong Doxygen thì có một lỗi trong Doxygen


-14

Tôi không thấy vấn đề của bạn với cảnh báo. Tài liệu này trong tiêu đề phương thức / hàm mà trình biên dịch xy sẽ đưa ra cảnh báo (đúng) ở đây, nhưng các biến đó là cần thiết cho nền tảng z.

Cảnh báo là chính xác, không cần phải tắt nó. Nó không làm mất hiệu lực chương trình - nhưng nó phải được ghi lại, rằng có một lý do.


20
Vấn đề là, nếu bạn có hàng trăm hoặc hàng ngàn cảnh báo như vậy, bạn có thể bỏ lỡ một cảnh báo hữu ích. (Hai lần tôi đã ở trong tình huống phải vượt qua hàng vạn cảnh báo, loại bỏ hầu hết và tìm ra một vài điều thực sự hữu ích một khi đã ám chỉ các lỗi nghiêm trọng.) Luôn luôn tốt để biên dịch mà không cần cảnh báo, nếu có thể ở mức cảnh báo cao nhất.
sbi

4
Trong một dự án tôi đã làm vào năm ngoái, tôi đã bật mức cảnh báo cao nhất và nhận được ~ 10.000 cảnh báo. Chỉ một vài chục là thực sự hữu ích. Trong số đó đã ẩn giấu khoảng một tá lỗi thực sự khó chịu, nhưng phải mất vài tuần đàn ông để dọn sạch cơ sở mã đến mức người ta thực sự có thể nhìn thấy một vài lỗi nghiêm trọng. Nếu mức cảnh báo luôn tăng và cơ sở mã được giữ không có cảnh báo, những lỗi đó sẽ không bao giờ xuất hiện trong mã.
sbi

1
xin lỗi - nhưng thực hiện phân tích mã tĩnh (sử dụng bất kỳ công cụ nào bạn có sẵn, ngay cả khi đó chỉ là trình biên dịch) muộn trong dự án giống như lập trình toàn bộ chương trình và khi bạn hoàn thành, nhấn biên dịch và hy vọng bạn không gặp lỗi.
Tobias Langner

2
@Richard: Tôi đã làm việc trên các dự án với hàng ngàn tệp nguồn. Một cảnh báo nhỏ ở đây và ở đó, ngay cả những tài liệu tốt, nhanh chóng bổ sung. Ngay cả khi bạn chỉ có hàng tá cảnh báo nhấp nháy trong quá trình xây dựng (thay vì hàng trăm hoặc hàng nghìn), việc phải tìm kiếm chúng riêng lẻ để xem liệu chúng là những cái mới hay tài liệu quá tốn thời gian và cuối cùng, đã thắng ' được thực hiện Do đó: Biên dịch ở mức cảnh báo cao nhất có thể với cảnh báo bằng không. Mọi cảnh báo xuất hiện sẽ được chú ý ngay lập tức, nhìn vào nó và được sửa chữa hoặc thay thế.
sbi

2
@sbi: turining ở mức cảnh báo cao nhất cho trình biên dịch của bạn là một số dạng phân tích mã tĩnh. Phân tích mã tĩnh chỉ là đọc mã mà không thực hiện nó và khấu trừ thông tin từ nó. Đó chính xác là những gì trình biên dịch làm khi kiểm tra các quy tắc của mình để cảnh báo.
Tobias Langner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.