Kiểm tra con trỏ NULL trong C / C ++ [đã đóng]


153

Trong một đánh giá mã gần đây, một người đóng góp đang cố gắng thực thi rằng tất cả các NULLkiểm tra về con trỏ được thực hiện theo cách sau:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

thay vì

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Tôi đồng ý rằng cách của anh ấy rõ ràng hơn một chút theo nghĩa rõ ràng là nói "Hãy chắc chắn rằng con trỏ này không phải là NULL", nhưng tôi sẽ phản bác lại rằng bằng cách nói rằng bất cứ ai làm việc với mã này đều hiểu rằng sử dụng biến con trỏ trong một iftuyên bố đang ngầm kiểm tra NULL. Ngoài ra tôi cảm thấy phương pháp thứ hai có cơ hội nhỏ hơn để giới thiệu một lỗi của ilk:

if (some_ptr = NULL)

đó chỉ là một nỗi đau tuyệt đối để tìm và gỡ lỗi.

Bạn thích cách nào và tại sao?


32
Có một phong cách nhất quán đôi khi quan trọng hơn phong cách bạn chọn.
Đánh dấu tiền chuộc

15
@Mark: Tính nhất quán luôn là yếu tố quan trọng nhất trong phong cách của bạn.
sbi

13
"Ngoài ra tôi cảm thấy phương thức thứ hai có cơ hội giới thiệu lỗi ilk nhỏ hơn: if (some_ptr = NULL)" Đó là lý do tại sao một số người sử dụng if (NULL == some_ptr), không thể gán cho NULL để bạn có thể nhận một lỗi biên dịch.
122302

14
hầu hết các trình biên dịch ngày nay đều cảnh báo về sự phân công trong các điều kiện - thật không may là quá nhiều nhà phát triển bỏ qua các cảnh báo của trình biên dịch.
Mike Ellery

10
Tôi không đồng ý với tuyên bố trên rằng tính nhất quán là điều quan trọng. Điều này không có gì để làm với sự nhất quán; dù sao cũng không phải là loại tốt. Đây là lĩnh vực mà chúng ta nên để các lập trình viên thể hiện phong cách riêng của họ. Nếu có một người không hiểu ngay về hình thức thì họ nên ngừng đọc mã ngay lập tức và xem xét thay đổi nghề nghiệp. Tôi sẽ nói nhiều hơn thế: Nếu có sự thống nhất về mỹ phẩm, bạn không thể tự động thực thi bằng một tập lệnh sau đó xóa nó. Cái giá của việc rút cạn đạo đức của con người là CÁCH quan trọng hơn những quyết định ngu ngốc mà mọi người đưa ra trong một cuộc họp.
wilmustell

Câu trả lời:


203

Theo kinh nghiệm của tôi, các bài kiểm tra của mẫu if (ptr)hoặc if (!ptr)được ưa thích. Họ không phụ thuộc vào định nghĩa của biểu tượng NULL. Họ không để lộ cơ hội cho sự phân công tình cờ. Và họ rõ ràng và cô đọng.

Chỉnh sửa: Như SoapBox chỉ ra trong một nhận xét, chúng tương thích với các lớp C ++ như auto_ptrlà các đối tượng đóng vai trò là con trỏ và cung cấp một chuyển đổi boolđể kích hoạt chính xác thành ngữ này. Đối với các đối tượng này, một so sánh rõ ràng NULLsẽ phải gọi một chuyển đổi sang con trỏ có thể có các tác dụng phụ ngữ nghĩa khác hoặc đắt hơn so với kiểm tra tồn tại đơn giản mà boolchuyển đổi ngụ ý.

Tôi có một ưu tiên cho mã nói ý nghĩa của nó mà không cần văn bản không cần thiết. if (ptr != NULL)có cùng ý nghĩa với if (ptr)nhưng với chi phí đặc thù dư thừa. Điều hợp lý tiếp theo là viết if ((ptr != NULL) == TRUE)và theo cách đó là sự điên rồ. Ngôn ngữ C rõ ràng là một boolean được kiểm tra bởi if, whilehoặc tương tự có ý nghĩa cụ thể của giá trị khác không là đúng và không là sai. Sự dư thừa không làm cho nó rõ ràng hơn.


23
Ngoài ra, chúng tương thích với các lớp trình bao bọc con trỏ (shared_ptr, auto_ptr, scoped_tr, v.v.) thường ghi đè lên toán tử bool (hoặc safe_bool).
SoapBox

2
Tôi đang bỏ phiếu đây là "câu trả lời" đơn giản là do tham chiếu đến auto_ptr thêm vào cuộc thảo luận. Sau khi viết câu hỏi, rõ ràng điều này thực sự chủ quan hơn bất cứ điều gì khác và có lẽ không phù hợp với "câu trả lời" của stackoverflow vì bất kỳ câu nào trong số này có thể "chính xác" tùy theo hoàn cảnh.
Bryan Marble

2
@Bryan, tôi tôn trọng điều đó, và nếu câu trả lời "tốt hơn" xuất hiện, xin vui lòng di chuyển dấu kiểm khi cần thiết. Còn về sự chủ quan thì đúng là chủ quan. Nhưng ít nhất đó là một cuộc thảo luận chủ quan trong đó có những vấn đề khách quan không phải lúc nào cũng rõ ràng khi câu hỏi được đặt ra. Đường kẻ mờ. Và chủ quan ... ;-)
RBerteig

1
Một điều quan trọng cần lưu ý: cho đến gần đây, tôi đã ủng hộ việc viết "if (ptr)" thay vì "if (ptr! = NULL)" hoặc "if (ptr! = Nullptr)" vì nó ngắn gọn hơn mã dễ đọc hơn. Nhưng tôi chỉ phát hiện ra rằng cảnh báo / lỗi tăng (so sánh) để so sánh với NULL / nullptr trên các trình biên dịch gần đây. Vì vậy, nếu bạn muốn kiểm tra một con trỏ, tốt hơn là thực hiện "if (ptr! = NULL)" thay vì "if (ptr)". Nếu bạn khai báo nhầm ptr là int, kiểm tra đầu tiên sẽ đưa ra cảnh báo / lỗi, trong khi kiểm tra sau sẽ biên dịch mà không có bất kỳ cảnh báo nào.
Arnaud

1
@David Wong Không, đó không phải là ý nghĩa. Ví dụ tại trang đó là chuỗi hai dòng int y = *x; if (!x) {...}trong đó trình tối ưu hóa được phép đưa ra lý do vì *xsẽ không được xác định nếu xlà NULL, nó không phải là NULL và do đó !xphải là FALSE. Biểu hiện không phải!ptr là hành vi không xác định. Trong ví dụ, nếu thử nghiệm được thực hiện trước khi sử dụng , thì trình tối ưu hóa sẽ sai khi loại bỏ thử nghiệm. *x
RBerteig

52

if (foo)là đủ rõ ràng. Sử dụng nó.


25

Tôi sẽ bắt đầu với điều này: tính nhất quán là vua, quyết định ít quan trọng hơn tính nhất quán trong cơ sở mã của bạn.

Trong C ++

NULL được định nghĩa là 0 hoặc 0L trong C ++.

Nếu bạn đã đọc Ngôn ngữ lập trình C ++, Bjarne Stroustrup gợi ý sử dụng 0rõ ràng để tránh NULLmacro khi thực hiện bài tập , tôi không chắc liệu anh ta có làm như vậy với các so sánh hay không, đã một thời gian kể từ khi tôi đọc cuốn sách, tôi nghĩ anh ta đã làm if(some_ptr)không có một so sánh rõ ràng nhưng tôi mờ về điều đó.

Lý do cho điều này là vì NULLmacro là lừa đảo (vì gần như tất cả các macro) nó thực sự là 0theo nghĩa đen, không phải là một loại duy nhất như tên cho thấy nó có thể. Tránh macro là một trong những hướng dẫn chung trong C ++. Mặt khác, 0trông giống như một số nguyên và nó không phải là khi so sánh với hoặc được gán cho con trỏ. Cá nhân tôi có thể đi một trong hai cách, nhưng thông thường tôi bỏ qua so sánh rõ ràng (mặc dù một số người không thích điều này có lẽ là lý do tại sao bạn có một cộng tác viên đề nghị thay đổi bằng mọi cách).

Bất kể cảm xúc cá nhân, điều này phần lớn là một sự lựa chọn ít xấu xa nhất vì không có một phương pháp đúng đắn.

Điều này là rõ ràng và là một thành ngữ phổ biến và tôi thích nó, không có cơ hội vô tình gán một giá trị trong khi so sánh và nó đọc rõ ràng:

if(some_ptr){;}

Điều này là rõ ràng nếu bạn biết đó some_ptrlà một loại con trỏ, nhưng nó cũng có thể trông giống như một so sánh số nguyên:

if(some_ptr != 0){;}

Điều này rất rõ ràng, trong những trường hợp thông thường, nó có ý nghĩa ... Nhưng đó là một sự trừu tượng bị rò rỉ, NULLthực sự 0theo nghĩa đen và cuối cùng có thể dễ dàng bị lạm dụng:

if(some_ptr != NULL){;}

C ++ 0x có nullptr hiện là phương thức ưa thích vì nó rõ ràng và chính xác, chỉ cần cẩn thận về việc gán ngẫu nhiên:

if(some_ptr != nullptr){;}

Cho đến khi bạn có thể di chuyển sang C ++ 0x, tôi sẽ cho rằng thật lãng phí thời gian để lo lắng về phương pháp nào bạn sử dụng, tất cả đều không đủ, đó là lý do tại sao nullptr được phát minh (cùng với các vấn đề lập trình chung đưa ra chuyển tiếp hoàn hảo .) Điều quan trọng nhất là duy trì tính nhất quán.

Trong C

C là một con thú khác nhau.

Trong C NULL có thể được định nghĩa là 0 hoặc là ((void *) 0), C99 cho phép thực hiện các hằng số con trỏ null được xác định. Vì vậy, nó thực sự đi vào định nghĩa của NULL và bạn sẽ phải kiểm tra nó trong thư viện chuẩn của mình.

Macro rất phổ biến và nói chung chúng được sử dụng rất nhiều để bù đắp cho những thiếu sót trong hỗ trợ lập trình chung trong ngôn ngữ và những thứ khác. Ngôn ngữ đơn giản hơn nhiều và sự phụ thuộc vào bộ xử lý trước phổ biến hơn.

Từ quan điểm này, tôi có thể khuyên bạn nên sử dụng NULLđịnh nghĩa macro trong C.


tl; dr và mặc dù bạn nói đúng về 0C ++, nó có ý nghĩa trong C : (void *)0. 0thích hợp hơn NULLtrong C ++, vì lỗi loại có thể là PITA.
Matt Joiner

@Matt: Nhưng NULLdù sao cũng là con số không.
GManNickG

@GMan: Không có trên hệ thống của tôi: #define NULL ((void *)0)từlinux/stddef.h
Matt Joiner

@Matt: Trong C ++. Bạn nói 0 là tốt hơn, nhưng NULLphải bằng không.
GManNickG

Đây là một điểm tốt, tôi đã bỏ qua đề cập đến nó và tôi đang cải thiện câu trả lời của mình bằng cách đề xuất nó. ANSI C có thể có NULL được định nghĩa là ((void *) 0), C ++ định nghĩa NULL là 0. Tôi chưa tìm ra tiêu chuẩn cho điều này trực tiếp nhưng hiểu biết của tôi là trong C ++ NULL có thể là 0 hoặc 0L.
M2tM

19

Tôi sử dụng if (ptr), nhưng điều này là hoàn toàn không đáng tranh luận.

Tôi thích cách của tôi vì nó ngắn gọn, mặc dù những người khác nói == NULLgiúp đọc dễ dàng và rõ ràng hơn. Tôi thấy họ đến từ đâu, tôi chỉ không đồng ý những thứ bổ sung giúp mọi việc dễ dàng hơn. (Tôi ghét macro, vì vậy tôi thiên vị.) Tùy bạn.

Tôi không đồng ý với lập luận của bạn. Nếu bạn không nhận được cảnh báo cho các bài tập trong một điều kiện, bạn cần phải tăng mức cảnh báo của mình lên. Đơn giản như vậy. (Và vì tình yêu là tốt, đừng chuyển chúng đi.)

Lưu ý trong C ++ 0x, chúng ta có thể làm if (ptr == nullptr), mà với tôi không đẹp hơn đọc. (Một lần nữa, tôi ghét macro. Nhưng nullptrthật tuyệt.) Tôi vẫn làm if (ptr), mặc dù, chỉ vì đó là những gì tôi đã từng làm.


hy vọng thêm 5 năm kinh nghiệm đã thay đổi suy nghĩ của bạn :) nếu C ++ / C có toán tử giống như if_exist (), thì nó sẽ có ý nghĩa.
magulla

10

Thành thật mà nói, tôi không thấy lý do tại sao nó quan trọng. Một trong hai là khá rõ ràng và bất cứ ai có kinh nghiệm vừa phải với C hoặc C ++ nên hiểu cả hai. Một bình luận, mặc dù:

Nếu bạn có kế hoạch nhận ra lỗi và không tiếp tục thực thi chức năng (nghĩa là bạn sẽ ném ngoại lệ hoặc trả lại mã lỗi ngay lập tức), bạn nên biến nó thành mệnh đề bảo vệ:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Bằng cách này, bạn tránh được "mã mũi tên."


ai đó đã chỉ vào đây kukuruku.co/hub/programming/i-do-not-ledge-c đó if(!p)là hành vi không xác định. Nó có thể làm việc hoặc không.
David Wong

@David 天宇 Wong nếu bạn đang nói về ví dụ # 2 tại liên kết đó, đó if(!p)không phải là hành vi không xác định, đó là dòng trước nó. Và if(p==NULL)sẽ có cùng một vấn đề.
Đánh dấu tiền chuộc

8

Cá nhân tôi luôn sử dụng if (ptr == NULL)vì nó làm cho ý định của tôi rõ ràng, nhưng tại thời điểm này, đó chỉ là một thói quen.

Sử dụng =thay thế ==sẽ bị bắt bởi bất kỳ trình biên dịch có thẩm quyền với các cài đặt cảnh báo chính xác.

Điểm quan trọng là chọn một phong cách nhất quán cho nhóm của bạn và bám sát nó. Cho dù bạn đi theo con đường nào, cuối cùng bạn cũng sẽ quen với nó và việc mất ma sát khi làm việc trong mã của người khác sẽ được hoan nghênh.


7

Chỉ cần thêm một điểm có lợi cho foo == NULLthực tiễn: Nếu foolà, giả sử, int *hoặc bool *, thì if (foo)kiểm tra có thể vô tình được người đọc hiểu là kiểm tra giá trị của điểm, tức là if (*foo). Sự NULLso sánh ở đây là một lời nhắc nhở rằng chúng ta đang nói về một con trỏ.

Nhưng tôi cho rằng một quy ước đặt tên tốt làm cho tranh luận này được đưa ra.


4

Trên thực tế, tôi sử dụng cả hai biến thể.

Có những tình huống, trước tiên bạn kiểm tra tính hợp lệ của một con trỏ và nếu đó là NULL, bạn chỉ cần trả về / thoát khỏi hàm. (Tôi biết điều này có thể dẫn đến cuộc thảo luận "nếu một chức năng chỉ có một điểm thoát")

Hầu hết thời gian, bạn kiểm tra con trỏ, sau đó làm những gì bạn muốn và sau đó giải quyết trường hợp lỗi. Kết quả có thể là mã thụt lề x lần xấu xí với nhiều if.


1
Cá nhân tôi ghét mã mũi tên sẽ dẫn đến việc sử dụng một điểm thoát. Tại sao không thoát nếu tôi đã biết câu trả lời?
ruslik

1
@ruslik: trong C (hoặc kiểu C-with-class C ++), việc có nhiều trả về khiến việc dọn dẹp trở nên khó khăn hơn. Trong C ++ "thực" rõ ràng không phải là vấn đề vì việc dọn dẹp của bạn được xử lý bởi RAII, nhưng một số lập trình viên (vì lý do ít nhiều hợp lệ) bị mắc kẹt trong quá khứ và từ chối, hoặc không được phép, dựa vào RAII .
jalf

@jalf trong những trường hợp như vậy a gotocó thể rất tiện dụng. Ngoài ra, trong nhiều trường hợp malloc(), việc cần dọn dẹp có thể được thay thế nhanh hơn alloca(). Tôi biết chúng không được khuyến nghị, nhưng chúng tồn tại vì một lý do (theo tôi, một nhãn có tên tốt gotohơn sạch hơn nhiều so với if/elsecây bị che khuất ).
ruslik

1
allocalà không chuẩn và hoàn toàn không an toàn. Nếu thất bại, chương trình của bạn sẽ nổ tung. Không có cơ hội để phục hồi và vì stack tương đối nhỏ, nên có khả năng thất bại. Khi thất bại, có thể bạn ghi đè lên đống và đưa ra các lỗ hổng thỏa hiệp đặc quyền. Không bao giờ sử dụng allocahoặc vlas trừ khi bạn có một ràng buộc nhỏ về kích thước sẽ được phân bổ (và sau đó bạn cũng có thể chỉ sử dụng một mảng bình thường).
R .. GitHub DỪNG GIÚP ICE

4

Ngôn ngữ lập trình C (K & R) sẽ giúp bạn kiểm tra null == ptr để tránh việc gán ngẫu nhiên.


23
K & R cũng sẽ hỏi "con trỏ thông minh là gì?" Mọi thứ đang thay đổi trong thế giới C ++.
Alex Emelianov

2
hay đúng hơn, mọi thứ đã thay đổi đã có trong C ++ trên thế giới";)
jalf

3
Trường hợp K & R nói rằng (ref) ở đâu? Họ không bao giờ sử dụng phong cách này cho mình. Trong K & R2 chương 2, họ thậm chí còn đề xuất if (!valid)ở trên if (valid == 0).
schot

6
Giải quyết xuống, folks. Anh ấy nói k & r, không phải K & R. Họ rõ ràng là ít nổi tiếng hơn vì tên viết tắt của họ thậm chí không được viết hoa.
jmucchiello

1
viết hoa chỉ làm bạn chậm lại
Derek

3

Nếu phong cách và định dạng sẽ là một phần trong các đánh giá của bạn, cần có một hướng dẫn về phong cách đã được thống nhất để đo lường. Nếu có một, làm những gì hướng dẫn phong cách nói. Nếu không có ai, các chi tiết như thế này sẽ được để lại khi chúng được viết. Đó là một sự lãng phí thời gian và năng lượng, và làm mất tập trung vào những gì các bài đánh giá mã thực sự nên được phát hiện ra. Nghiêm túc mà nói, không có hướng dẫn về phong cách, tôi sẽ cố gắng KHÔNG thay đổi mã như thế này như một vấn đề nguyên tắc, ngay cả khi nó không sử dụng quy ước mà tôi thích.

Và không phải là vấn đề, nhưng sở thích cá nhân của tôi là if (ptr). Ý nghĩa rõ ràng ngay lập tức với tôi hơn cảif (ptr == NULL) .

Có lẽ anh ta đang cố gắng nói rằng tốt hơn là xử lý các điều kiện lỗi trước con đường hạnh phúc? Trong trường hợp đó tôi vẫn không đồng ý với người đánh giá. Tôi không biết rằng có một quy ước được chấp nhận cho điều này, nhưng theo tôi, điều kiện "bình thường" nhất phải được ưu tiên hàng đầu trong bất kỳ tuyên bố if nào. Bằng cách đó, tôi đã ít phải đào bới để tìm ra chức năng là gì và hoạt động như thế nào.

Ngoại lệ cho điều này là nếu lỗi khiến tôi phải bảo lãnh từ chức năng hoặc tôi có thể khôi phục từ đó trước khi tiếp tục. Trong những trường hợp đó, tôi xử lý lỗi trước:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Bằng cách đối phó với tình trạng bất thường ở phía trước, tôi có thể chăm sóc nó và sau đó quên nó đi. Nhưng nếu tôi không thể quay lại con đường hạnh phúc bằng cách xử lý nó trước, thì nó sẽ được xử lý sau vụ án chính vì nó làm cho mã dễ hiểu hơn. Theo ý kiến ​​của tôi.

Nhưng nếu nó không theo hướng dẫn về phong cách thì đó chỉ là ý kiến ​​của tôi và ý kiến ​​của bạn cũng có giá trị. Hoặc tiêu chuẩn hóa hoặc không. Đừng để người đánh giá giả chuẩn hóa chỉ vì anh ta có ý kiến.


1

Đây là một trong những nguyên tắc cơ bản của cả hai ngôn ngữ mà con trỏ đánh giá về một loại và giá trị có thể được sử dụng làm biểu thức kiểm soát, booltrong C ++ và inttrong C. Chỉ cần sử dụng nó.


1
  • Con trỏ không phải là booleans
  • Trình biên dịch C / C ++ hiện đại phát ra cảnh báo khi bạn viết một if (foo = bar)cách tình cờ.

Vì vậy tôi thích

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

hoặc là

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Tuy nhiên, nếu tôi đang viết một bộ hướng dẫn về phong cách, tôi sẽ không đặt những thứ như thế này vào đó, tôi sẽ đặt những thứ như:

Hãy chắc chắn rằng bạn thực hiện kiểm tra null trên con trỏ.


2
Đúng là con trỏ không phải là booleans, nhưng trong C, if-statement không lấy booleans: chúng lấy biểu thức nguyên.
Ken

@Ken: đó là vì C bị phá vỡ trong khía cạnh đó. Về mặt khái niệm, đó là một biểu thức boolean và (theo tôi) nên được xử lý như vậy.
JeremyP

1
Một số ngôn ngữ có câu lệnh if chỉ kiểm tra null / not-null. Một số ngôn ngữ có câu lệnh if chỉ kiểm tra một số nguyên cho dấu (kiểm tra 3 chiều). Tôi thấy không có lý do gì để coi C "hỏng" vì họ đã chọn một khái niệm khác mà bạn thích. Có rất nhiều điều tôi ghét về C nhưng điều đó chỉ có nghĩa là mô hình chương trình C không giống với mô hình tinh thần của tôi, không phải là một trong hai chúng tôi (tôi hoặc C) bị phá vỡ.
Ken

@Ken: Boolean không phải là một con số hay một con trỏ về mặt khái niệm , Đừng bận tâm đến ngôn ngữ nào.
JeremyP

1
Tôi đã không nói rằng boolean là một con số hoặc con trỏ. Tôi đã nói rằng không có lý do gì để khẳng định rằng một câu lệnh if nên hoặc chỉ có thể có một biểu thức boolean, và đưa ra các mẫu phản biện, ngoài ra là một trong những câu hỏi trong tay. Rất nhiều ngôn ngữ lấy một cái gì đó không phải là boolean, và không chỉ theo cách "zero / nonzero" của C. Trong điện toán, có một câu lệnh if chấp nhận (chỉ) một boolean là một sự phát triển tương đối gần đây.
Ken

1

Tôi là một fan cuồng nhiệt của thực tế là C / C ++ không kiểm tra các loại trong điều kiện boolean trong if, forwhilebáo cáo. Tôi luôn luôn sử dụng như sau:

if (ptr)

if (!ptr)

thậm chí trên các số nguyên hoặc loại khác chuyển đổi thành bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Mã hóa theo phong cách này dễ đọc và rõ ràng hơn đối với tôi. Chỉ là ý kiến ​​cá nhân của tôi.

Gần đây, khi làm việc trên bộ vi điều khiển của máy quay phim 431, tôi đã nhận thấy rằng:

unsigned char chx;

if (chx) // ...

hiệu quả hơn

if (chx == 1) // ...

bởi vì trong trường hợp sau, trình biên dịch phải so sánh giá trị của chx với 1. Trong đó chx chỉ là một cờ đúng / sai.


1

Hầu hết các trình biên dịch mà tôi đã sử dụng ít nhất sẽ cảnh báo về ifbài tập mà không cần thêm cú pháp đường, vì vậy tôi không mua đối số đó. Điều đó nói rằng, tôi đã sử dụng cả chuyên nghiệp và không có ưu tiên cho một trong hai. Điều == NULLnày chắc chắn rõ ràng hơn theo ý kiến ​​của tôi.

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.