'Const static' có nghĩa là gì trong C và C ++?


117
const static int foo = 42;

Tôi đã thấy điều này trong một số mã ở đây trên StackOverflow và tôi không thể tìm ra nó làm gì. Sau đó, tôi thấy một số câu trả lời nhầm lẫn trên các diễn đàn khác. Tôi đoán tốt nhất là nó được sử dụng trong C để ẩn hằng số fookhỏi các mô-đun khác. Điều này có đúng không? Nếu vậy, tại sao mọi người sẽ sử dụng nó trong bối cảnh C ++ nơi bạn có thể tạo ra nó private?

Câu trả lời:


113

Nó có sử dụng trong cả C và C ++.

Như bạn đã đoán, staticphần giới hạn phạm vi của nó cho đơn vị biên dịch đó . Nó cũng cung cấp cho khởi tạo tĩnh. constchỉ nói với trình biên dịch để không cho phép bất cứ ai sửa đổi nó. Biến này được đặt trong phân đoạn dữ liệu hoặc bss tùy thuộc vào kiến ​​trúc và có thể nằm trong bộ nhớ được đánh dấu chỉ đọc.

Tất cả đó là cách C xử lý các biến này (hoặc cách C ++ xử lý các biến không gian tên). Trong C ++, một thành viên được đánh dấu staticđược chia sẻ bởi tất cả các phiên bản của một lớp nhất định. Cho dù đó là riêng tư hay không không ảnh hưởng đến thực tế là một biến được chia sẻ bởi nhiều trường hợp. Có consttrên đó sẽ cảnh báo bạn nếu có bất kỳ mã nào sẽ cố gắng sửa đổi điều đó.

Nếu nó hoàn toàn riêng tư, thì mỗi phiên bản của lớp sẽ có phiên bản riêng (mặc dù trình tối ưu hóa).


1
Ví dụ ban đầu là nói về một "biến riêng". Do đó, đây là một mebmer và tĩnh không ảnh hưởng đến liên kết. Bạn nên xóa "phần tĩnh giới hạn phạm vi của tệp đó".
Richard Corden

"Phần đặc biệt" được gọi là phân đoạn dữ liệu, nó chia sẻ với tất cả các biến toàn cục khác, chẳng hạn như "chuỗi" rõ ràng và mảng toàn cục. Điều này trái ngược với phân đoạn mã.
spoulson

@Richard - điều gì khiến bạn nghĩ nó là thành viên của một lớp? Không có gì trong câu hỏi nói rằng nó là. Nếu đó là thành viên của một lớp, thì bạn đúng, nhưng nếu đó chỉ là một biến được khai báo ở phạm vi toàn cầu, thì Chris đã đúng.
Graeme Perrow

1
Các poster ban đầu đề cập riêng tư như là một giải pháp tốt hơn có thể, nhưng không phải là vấn đề ban đầu.
Chris Arguin

@Graeme, OK vì vậy nó không "chắc chắn" là thành viên - tuy nhiên, câu trả lời này là đưa ra các câu lệnh chỉ áp dụng cho các thành viên không gian tên và các câu lệnh đó là sai đối với các biến thành viên. Với số phiếu mà lỗi có thể gây nhầm lẫn cho một người không quen thuộc với ngôn ngữ này - nó sẽ được sửa.
Richard Corden

212

Rất nhiều người đã đưa ra câu trả lời cơ bản nhưng không ai chỉ ra rằng trong C ++ constmặc định staticnamespacecấp độ (và một số đã cung cấp thông tin sai). Xem phần tiêu chuẩn C ++ 98 3.5.3.

Đầu tiên một số nền tảng:

Đơn vị dịch: Một tệp nguồn sau bộ xử lý trước (đệ quy) bao gồm tất cả các tệp bao gồm.

Liên kết tĩnh: Một biểu tượng chỉ có sẵn trong đơn vị dịch thuật của nó.

Liên kết ngoài: Một biểu tượng có sẵn từ các đơn vị dịch thuật khác.

namespacecấp độ

Điều này bao gồm không gian tên toàn cầu hay còn gọi là biến toàn cục .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

Ở cấp độ chức năng

staticcó nghĩa là giá trị được duy trì giữa các lệnh gọi hàm.
Các ngữ nghĩa của các staticbiến chức năng tương tự như các biến toàn cục ở chỗ chúng nằm trong phân đoạn dữ liệu của chương trình (chứ không phải ngăn xếp hoặc heap), xem câu hỏi này để biết thêm chi tiết về statictuổi thọ của biến.

classcấp độ

staticcó nghĩa là giá trị được chia sẻ giữa tất cả các thể hiện của lớp và constcó nghĩa là nó không thay đổi.


2
Ở cấp độ chức năng: tĩnh không bị thay thế với const, chúng có thể hoạt động khác const int *foo(int x) {const int b=x;return &b};so vớiconst int *foo(int x) {static const int b=x;return &b};
Hanczar

1
Câu hỏi là về cả C và C ++, vì vậy bạn nên bao gồm một lưu ý về việc constchỉ ngụ ý statictrong phần sau.
Nikolai Ruhe

@Motti: Câu trả lời tuyệt vời. Bạn có thể vui lòng giải thích những gì làm cho những gì dư thừa ở cấp độ chức năng? Bạn đang nói consttuyên bố ngụ ý staticlà tốt? Như trong, nếu bạn bỏ constđi và sửa đổi giá trị, tất cả các giá trị sẽ được sửa đổi?
Cookie

1
@Motti constkhông ngụ ý tĩnh ở cấp độ chức năng, đó sẽ là một cơn ác mộng đồng thời (const! = Biểu thức không đổi), mọi thứ ở cấp độ chức năng đều được ngầm định auto. Vì câu hỏi này cũng được gắn thẻ [c] , tuy nhiên , tôi nên đề cập rằng mức độ toàn cầu const intđược ngầm định externtrong C. Tuy nhiên, các quy tắc bạn có ở đây mô tả hoàn hảo C ++.
Ryan Hained

1
Và trong C ++, trong cả ba trường hợp, staticchỉ ra rằng biến đó là thời lượng tĩnh (chỉ tồn tại một bản sao, tồn tại từ đầu chương trình cho đến khi kết thúc) và có liên kết bên trong / tĩnh nếu không được chỉ định (điều này bị ghi đè bởi hàm liên kết cho các biến tĩnh cục bộ hoặc liên kết của lớp cho các thành viên tĩnh). Sự khác biệt chính là những gì điều này ngụ ý trong mỗi tình huống có staticgiá trị.
Thời gian của Justin - Phục hồi Monica

45

Dòng mã đó thực sự có thể xuất hiện trong một số bối cảnh khác nhau và mặc dù nó hoạt động gần giống nhau, có những khác biệt nhỏ.

Phạm vi không gian tên

// foo.h
static const int i = 0;

' i' sẽ hiển thị trong mọi đơn vị dịch thuật bao gồm tiêu đề. Tuy nhiên, trừ khi bạn thực sự sử dụng địa chỉ của đối tượng (ví dụ: ' &i'), tôi khá chắc chắn rằng trình biên dịch sẽ coi ' i' đơn giản là một loại an toàn 0. Trường hợp thêm hai đơn vị dịch thuật lấy ' &i' thì địa chỉ sẽ khác nhau cho mỗi đơn vị dịch thuật.

// foo.cc
static const int i = 0;

' i' có liên kết nội bộ và do đó không thể được gọi từ bên ngoài đơn vị dịch thuật này. Tuy nhiên, một lần nữa trừ khi bạn sử dụng địa chỉ của nó, rất có thể nó sẽ được coi là loại an toàn 0.

Một điều đáng để chỉ ra, đó là tuyên bố sau:

const int i1 = 0;

chính xác giống như static const int i = 0. Một biến trong một không gian tên được khai báo với constvà không được khai báo rõ ràng với externlà hoàn toàn tĩnh. Nếu bạn nghĩ về điều này, đó là ý định của ủy ban C ++ khi cho phép constcác biến được khai báo trong các tệp tiêu đề mà không phải luôn cần statictừ khóa để tránh phá vỡ ODR.

Phạm vi lớp học

class A {
public:
  static const int i = 0;
};

Trong ví dụ trên, tiêu chuẩn chỉ định rõ ràng rằng ' i' không cần phải được xác định nếu không yêu cầu địa chỉ của nó. Nói cách khác, nếu bạn chỉ sử dụng ' i' dưới dạng 0 an toàn thì trình biên dịch sẽ không xác định nó. Một điểm khác biệt giữa các phiên bản lớp và không gian tên là địa chỉ của ' i' (nếu được sử dụng trong hai đơn vị dịch thuật nhiều quặng hơn) sẽ giống nhau cho thành viên lớp. Địa chỉ được sử dụng, bạn phải có định nghĩa cho địa chỉ đó:

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address

2
+1 để chỉ ra rằng const tĩnh giống như const trong phạm vi không gian tên.
Plumator

Thực tế không có sự khác biệt giữa việc đặt trong "foo.h" hoặc trong "foo.cc" vì .h chỉ đơn giản được bao gồm khi biên dịch đơn vị dịch thuật.
Mikhail

2
@Mikhail: Bạn đã đúng. Có một giả định rằng một tiêu đề có thể được bao gồm trong nhiều TU và vì vậy thật hữu ích khi nói về điều đó một cách riêng biệt.
Richard Corden

24

Đó là một tối ưu hóa không gian nhỏ.

Khi bạn nói

const int foo = 42;

Bạn không định nghĩa một hằng số, nhưng tạo một biến chỉ đọc. Trình biên dịch đủ thông minh để sử dụng 42 bất cứ khi nào nó nhìn thấy foo, nhưng nó cũng sẽ phân bổ không gian trong vùng dữ liệu khởi tạo cho nó. Điều này được thực hiện bởi vì, như được định nghĩa, foo có liên kết bên ngoài. Một đơn vị biên dịch khác có thể nói:

extern const int foo;

Để có được quyền truy cập vào giá trị của nó. Đó không phải là một thực hành tốt vì đơn vị biên dịch đó không biết giá trị của foo là gì. Nó chỉ biết đó là một int int và phải tải lại giá trị từ bộ nhớ bất cứ khi nào nó được sử dụng.

Bây giờ, bằng cách tuyên bố rằng nó là tĩnh:

static const int foo = 42;

Trình biên dịch có thể thực hiện tối ưu hóa thông thường của nó, nhưng nó cũng có thể nói "này, không ai ngoài đơn vị biên dịch này có thể thấy foo và tôi biết nó luôn luôn là 42 nên không cần phải phân bổ bất kỳ không gian nào cho nó."

Tôi cũng cần lưu ý rằng trong C ++, cách ưa thích để ngăn tên thoát khỏi đơn vị biên dịch hiện tại là sử dụng một không gian tên ẩn danh:

namespace {
    const int foo = 42; // same as static definition above
}

1
, u đã đề cập mà không sử dụng tĩnh "nó cũng sẽ phân bổ không gian trong vùng dữ liệu khởi tạo cho nó". và bằng cách sử dụng tĩnh "không cần phân bổ bất kỳ khoảng trống nào cho nó". (từ đó trình biên dịch đang chọn val sau đó?) bạn có thể giải thích về thuật ngữ heap và stack nơi biến đang được lưu trữ. Hãy sửa cho tôi nếu tôi hiểu nó Sai lầm .
Nihar

@ N.Nihar - Vùng dữ liệu tĩnh là một đoạn bộ nhớ có kích thước cố định chứa tất cả dữ liệu có liên kết tĩnh. Nó được "phân bổ" bởi quá trình tải chương trình vào bộ nhớ. Nó không phải là một phần của chồng hoặc đống.
Ferruccio

Điều gì xảy ra nếu tôi có một hàm trả về một con trỏ tới foo? Điều đó có phá vỡ sự tối ưu hóa không?
nw.

@nw: Vâng, nó sẽ phải.
Ferruccio

8

Nó thiếu một 'int'. Nó nên là:

const static int foo = 42;

Trong C và C ++, nó khai báo một hằng số nguyên với phạm vi tệp cục bộ có giá trị 42.

Tại sao 42? Nếu bạn chưa biết (và thật khó tin là bạn không biết), thì đó là một lời giới thiệu cho Câu trả lời cho Cuộc sống, Vũ trụ và Mọi thứ .


Cảm ơn ... bây giờ mọi lúc ... cho phần còn lại của cuộc đời tôi ... khi tôi nhìn thấy 42, tôi sẽ luôn luôn nghĩ về điều này. haha
Inisheer

Đây là bằng chứng tích cực rằng vũ trụ được tạo ra bằng cách có 13 ngón tay (câu hỏi và câu trả lời thực sự khớp với cơ sở 13).
paxdiablo

Đó là những con chuột. 3 ngón chân trên mỗi bàn chân, cộng với một cái đuôi mang lại cho bạn cơ sở 13.
KeithB

Bạn thực sự không cần 'int' trong một tuyên bố, mặc dù nó chắc chắn là tốt để viết nó ra. C luôn giả sử các loại 'int' theo mặc định. Thử nó!
ephemient

"với phạm vi tệp cục bộ của giá trị 42" ?? hoặc là cho toàn bộ đơn vị biên dịch?
aniliitb10

4

Trong C ++,

static const int foo = 42;

là cách ưa thích để xác định và sử dụng hằng. Tức là sử dụng cái này chứ không phải

#define foo 42

bởi vì nó không lật đổ hệ thống an toàn kiểu.


4

Đối với tất cả các câu trả lời tuyệt vời, tôi muốn thêm một chi tiết nhỏ:

Nếu bạn viết các trình cắm (ví dụ: các thư viện DLL hoặc .so được tải bởi hệ thống CAD), thì tĩnh là trình bảo vệ cuộc sống tránh các xung đột tên như thế này:

  1. Hệ thống CAD tải một plugin A, có "const int foo = 42;" trong đó.
  2. Hệ thống tải một plugin B, có "const int foo = 23;" trong đó.
  3. Do đó, plugin B sẽ sử dụng giá trị 42 cho foo, bởi vì trình tải plugin sẽ nhận ra rằng đã có một "foo" với liên kết bên ngoài.

Thậm chí tệ hơn: Bước 3 có thể hoạt động khác nhau tùy thuộc vào tối ưu hóa trình biên dịch, cơ chế tải plugin, v.v.

Tôi đã gặp vấn đề này một lần với hai hàm trợ giúp (cùng tên, hành vi khác nhau) trong hai plugin. Tuyên bố họ giải quyết vấn đề tĩnh.


Một cái gì đó có vẻ kỳ lạ về sự va chạm tên giữa hai trình cắm, điều này khiến tôi kiểm tra bản đồ liên kết cho một trong nhiều DLL của tôi, định nghĩa m_hDfltHeap là một điều khiển với liên kết bên ngoài. Chắc chắn, có tất cả thế giới để xem và sử dụng, được liệt kê trong bản đồ liên kết là _m_hDfltHeap. Tôi đã quên tất cả về factoid này.
David A. Gray

4

Theo thông số kỹ thuật của C99 / GNU99:

  • static

    • là chỉ định lớp lưu trữ

    • Các đối tượng của phạm vi cấp tệp theo mặc định có liên kết bên ngoài

    • các đối tượng của phạm vi mức tệp với trình xác định tĩnh có liên kết nội bộ
  • const

    • là loại vòng loại (là một phần của loại)

    • từ khóa được áp dụng cho ví dụ bên trái ngay lập tức - tức là

      • MyObj const * myVar; - con trỏ không đủ tiêu chuẩn để tạo thành loại đối tượng đủ điều kiện

      • MyObj * const myVar; - const con trỏ đủ điều kiện để loại đối tượng không đủ tiêu chuẩn

    • Cách sử dụng ngoài cùng - áp dụng cho loại đối tượng, không thay đổi

      • const MyObj * myVar; - con trỏ không đủ tiêu chuẩn để tạo thành loại đối tượng đủ điều kiện

THUS:

static NSString * const myVar; - con trỏ không đổi đến chuỗi bất biến với liên kết nội bộ.

Sự vắng mặt của statictừ khóa sẽ khiến tên biến thành toàn cầu và có thể dẫn đến xung đột tên trong ứng dụng.


4

C ++ 17 inlinebiến

Nếu bạn đã tìm kiếm "C ++ const static", thì đây rất có thể là thứ bạn thực sự muốn sử dụng là các biến nội tuyến của C ++ 17 .

Tính năng C ++ 17 tuyệt vời này cho phép chúng tôi:

  • thuận tiện chỉ sử dụng một địa chỉ bộ nhớ duy nhất cho mỗi hằng số
  • lưu trữ dưới dạng constexpr: Làm thế nào để khai báo constexpr extern?
  • làm điều đó trong một dòng duy nhất từ ​​một tiêu đề

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

không phải thế

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

không chính xác

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Biên dịch và chạy:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub ngược dòng .

Xem thêm: Làm thế nào để các biến nội tuyến hoạt động?

Tiêu chuẩn C ++ về các biến nội tuyến

Tiêu chuẩn C ++ đảm bảo rằng các địa chỉ sẽ giống nhau. Dự thảo tiêu chuẩn C ++ 17 N4659 10.1.6 "Trình xác định nội tuyến":

6 Hàm nội tuyến hoặc biến có liên kết ngoài sẽ có cùng địa chỉ trong tất cả các đơn vị dịch.

cppreference https://en.cppreference.com/w/cpp/lingu/inline giải thích rằng nếu statickhông được đưa ra, thì nó có liên kết bên ngoài.

Thực hiện biến nội tuyến GCC

Chúng ta có thể quan sát cách nó được thực hiện với:

nm main.o notmain.o

trong đó có:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

man nmnói về u:

"u" Biểu tượng là một biểu tượng toàn cầu độc đáo. Đây là một phần mở rộng GNU cho tập hợp các ràng buộc ký hiệu ELF tiêu chuẩn. Đối với một biểu tượng như vậy, trình liên kết động sẽ đảm bảo rằng trong toàn bộ quá trình chỉ có một biểu tượng có tên và loại được sử dụng.

vì vậy chúng tôi thấy rằng có một phần mở rộng ELF dành riêng cho việc này.

Tiền C ++ 17: extern const

Trước C ++ 17 và trong C, chúng ta có thể đạt được hiệu ứng rất giống với extern const , điều này sẽ dẫn đến một vị trí bộ nhớ duy nhất được sử dụng.

Nhược điểm trên inlinelà:

  • không thể thực hiện biến constexprvới kỹ thuật này, chỉ inlinecho phép: Làm thế nào để khai báo constexpr extern?
  • nó ít thanh lịch hơn khi bạn phải khai báo và định nghĩa biến riêng trong tệp tiêu đề và cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

không chính xác

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

không phải thế

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub ngược dòng .

Tiêu đề thay thế Pre-C ++ 17

Đây không phải là externgiải pháp tốt, nhưng chúng hoạt động và chỉ chiếm một vị trí bộ nhớ duy nhất:

Một constexprhàm, bởi vì constexprngụ ýinlineinline cho phép (buộc) định nghĩa xuất hiện trên mọi đơn vị dịch thuật :

constexpr int shared_inline_constexpr() { return 42; }

và tôi cá rằng bất kỳ trình biên dịch tử tế nào cũng sẽ thực hiện cuộc gọi.

Bạn cũng có thể sử dụng một consthoặc constexprbiến tĩnh như trong:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

nhưng bạn không thể làm những việc như lấy địa chỉ của nó, nếu không nó sẽ trở nên khó sử dụng, xem thêm: Xác định các thành viên dữ liệu tĩnh constexpr

C

Trong C, tình huống giống như C ++ trước C ++ 17, tôi đã tải lên một ví dụ tại: "static" nghĩa là gì trong C?

Sự khác biệt duy nhất là trong C ++, constngụ ý staticcho toàn cầu, nhưng nó không có trong ngữ nghĩa C: C ++ của `static const` vs` const`

Bất kỳ cách nào để hoàn toàn nội tuyến nó?

TODO: có cách nào để hoàn toàn nội tuyến biến, mà không sử dụng bất kỳ bộ nhớ nào không?

Giống như những gì bộ tiền xử lý làm.

Điều này sẽ yêu cầu bằng cách nào đó:

  • cấm hoặc phát hiện nếu địa chỉ của biến được lấy
  • thêm thông tin đó vào các tệp đối tượng ELF và để LTO tối ưu hóa nó lên

Liên quan:

Đã thử nghiệm trong Ubuntu 18.10, GCC 8.2.0.


2

Có, nó ẩn một biến trong một mô-đun từ các mô-đun khác. Trong C ++, tôi sử dụng nó khi tôi không muốn / cần thay đổi tệp .h sẽ kích hoạt việc xây dựng lại các tệp khác không cần thiết. Ngoài ra, tôi đặt tĩnh trước:

static const int foo = 42;

Ngoài ra, tùy thuộc vào việc sử dụng nó, trình biên dịch thậm chí sẽ không phân bổ dung lượng lưu trữ cho nó và chỉ đơn giản là "nội tuyến" giá trị mà nó được sử dụng. Không có tĩnh, trình biên dịch không thể cho rằng nó không được sử dụng ở nơi khác và không thể nội tuyến.


2

Hằng số toàn cầu này chỉ hiển thị / có thể truy cập trong mô-đun biên dịch (tệp .cpp). BTW sử dụng tĩnh cho mục đích này không được chấp nhận. Sử dụng tốt hơn một không gian tên ẩn danh và enum:

namespace
{
  enum
  {
     foo = 42
  };
}

điều này sẽ buộc trình biên dịch không coi foo là hằng số và như vậy gây cản trở tối ưu hóa.
Nils Pipenbrinck

giá trị của enums luôn không đổi vì vậy tôi không thấy điều này sẽ cản trở bất kỳ sự tối ưu hóa nào
Roskoto

à - đúng rồi .. lỗi của tôi nghĩ rằng bạn đã sử dụng một biến int đơn giản.
Nils Pipenbrinck

Roskoto, tôi không rõ những gì có lợi enumtrong bối cảnh này. Quan tâm đến công phu? Như vậy enumsthường chỉ được sử dụng để ngăn trình biên dịch phân bổ bất kỳ khoảng trống nào cho giá trị (mặc dù trình biên dịch hiện đại không cần enumhack này cho nó) và để ngăn việc tạo con trỏ tới giá trị.
Konrad Rudolph

Konrad, chính xác thì bạn gặp vấn đề gì khi sử dụng enum trong trường hợp này? Enums được sử dụng khi bạn cần ints không đổi, đó chính xác là trường hợp.
Roskoto

1

Làm cho nó riêng tư vẫn có nghĩa là nó xuất hiện trong tiêu đề. Tôi có xu hướng sử dụng cách "yếu nhất" mà hoạt động. Xem bài viết kinh điển này của Scott Meyers: http://www.ddj.com/cpp/184401197 (đó là về các chức năng, nhưng cũng có thể được áp dụng ở đây).

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.