Truy cập một mảng ngoài giới hạn không có lỗi, tại sao?


177

Tôi đang gán các giá trị trong một chương trình C ++ ngoài giới hạn như thế này:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    return 0;
}

Chương trình in 34. Nó không nên là có thể. Tôi đang sử dụng g ++ 4.3.3

Dưới đây là biên dịch và chạy lệnh

$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4

Chỉ khi gán array[3000]=3000nó mới cho tôi một lỗi phân khúc.

Nếu gcc không kiểm tra giới hạn mảng, làm thế nào tôi có thể chắc chắn liệu chương trình của mình có đúng không, vì nó có thể dẫn đến một số vấn đề nghiêm trọng sau này?

Tôi đã thay thế đoạn mã trên bằng

vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;

và điều này cũng không tạo ra lỗi.


3
Câu hỏi liên quan: stackoverflow.com/questions/671703/
Mạnh

15
Mã là lỗi, tất nhiên, nhưng nó tạo ra hành vi không xác định . Không xác định có nghĩa là nó có thể hoặc không thể chạy đến khi hoàn thành. Không có gì đảm bảo cho một vụ tai nạn.
dmckee --- ex-moderator mèo con

4
Bạn có thể chắc chắn rằng chương trình của bạn là chính xác bằng cách không vặn vẹo xung quanh với các mảng thô. Các lập trình viên C ++ nên sử dụng các lớp container thay thế, ngoại trừ trong lập trình nhúng / HĐH. Đọc này vì lý do để người dùng container. parashift.com/c++-faq-lite/containers.html
jkeys

8
Hãy nhớ rằng các vectơ không nhất thiết phải kiểm tra phạm vi bằng []. Sử dụng .at () thực hiện tương tự như [] nhưng kiểm tra phạm vi.
David Thornley

4
A vector không tự động thay đổi kích thước khi truy cập các phần tử ngoài giới hạn! Chỉ là UB!
Pavel Minaev

Câu trả lời:


364

Chào mừng bạn đến với người bạn thân nhất của lập trình viên C / C ++: Hành vi không xác định .

Có rất nhiều điều không được quy định bởi tiêu chuẩn ngôn ngữ, vì nhiều lý do. Đây là một trong số họ.

Nói chung, bất cứ khi nào bạn gặp phải hành vi không xác định, bất cứ điều gì có thể xảy ra. Ứng dụng có thể bị sập, nó có thể đóng băng, nó có thể đẩy ổ đĩa CD-ROM của bạn hoặc khiến quỷ thoát ra khỏi mũi bạn. Nó có thể định dạng ổ cứng của bạn hoặc gửi email tất cả nội dung khiêu dâm của bạn cho bà của bạn.

Nó thậm chí có thể, nếu bạn thực sự không may mắn, dường như hoạt động chính xác.

Ngôn ngữ chỉ đơn giản nói những gì sẽ xảy ra nếu bạn truy cập các phần tử trong giới hạn của một mảng. Nó không được xác định những gì xảy ra nếu bạn đi ra khỏi giới hạn. Nó dường như có thể hoạt động ngày hôm nay, trên trình biên dịch của bạn, nhưng nó không hợp pháp C hoặc C ++ và không có gì đảm bảo rằng nó sẽ vẫn hoạt động vào lần tới khi bạn chạy chương trình. Hoặc nó có dữ liệu cần thiết không ghi đè ngay cả bây giờ, và bạn chỉ cần đã không gặp phải những vấn đề, rằng nó sẽ gây ra - được nêu ra.

Về lý do tại sao không có giới hạn kiểm tra, có một số khía cạnh cho câu trả lời:

  • Mảng là phần còn lại từ mảng C. C là nguyên thủy như bạn có thể nhận được. Chỉ là một chuỗi các yếu tố với địa chỉ tiếp giáp. Không có giới hạn kiểm tra vì nó chỉ đơn giản là phơi bày bộ nhớ thô. Việc thực hiện một cơ chế kiểm tra giới hạn mạnh mẽ sẽ gần như không thể ở C.
  • Trong C ++, kiểm tra giới hạn có thể có trên các loại lớp. Nhưng một mảng vẫn là một tương thích C cũ đơn giản. Nó không phải là một lớp học. Hơn nữa, C ++ cũng được xây dựng trên một quy tắc khác khiến cho việc kiểm tra giới hạn trở nên không lý tưởng. Nguyên tắc hướng dẫn của C ++ là "bạn không trả tiền cho những gì bạn không sử dụng". Nếu mã của bạn là chính xác, bạn không cần kiểm tra giới hạn và bạn không nên bị buộc phải trả tiền cho việc kiểm tra giới hạn thời gian chạy.
  • Vì vậy, C ++ cung std::vectorcấp mẫu lớp, cho phép cả hai. operator[]được thiết kế để có hiệu quả. Tiêu chuẩn ngôn ngữ không yêu cầu nó thực hiện kiểm tra giới hạn (mặc dù nó cũng không cấm). Một vectơ cũng có at()chức năng thành viên được đảm bảo để thực hiện kiểm tra giới hạn. Vì vậy, trong C ++, bạn có được cả hai thế giới tốt nhất nếu bạn sử dụng một vectơ. Bạn có được hiệu suất giống như mảng mà không cần kiểm tra giới hạn bạn có khả năng sử dụng quyền truy cập được kiểm tra giới hạn khi bạn muốn.

5
@Jaif: chúng tôi đã sử dụng mảng này từ rất lâu rồi, nhưng tại sao vẫn chưa có bài kiểm tra nào để kiểm tra lỗi đơn giản như vậy?
seg.server.fault

7
Nguyên tắc thiết kế C ++ là nó không nên chậm hơn mã C tương đương và C không thực hiện kiểm tra ràng buộc mảng. Nguyên tắc thiết kế C về cơ bản là tốc độ vì nó nhằm mục đích lập trình hệ thống. Kiểm tra ràng buộc mảng mất thời gian, và vì vậy không được thực hiện. Đối với hầu hết các sử dụng trong C ++, dù sao bạn cũng nên sử dụng một container chứ không phải là mảng và bạn có thể lựa chọn kiểm tra ràng buộc hoặc không kiểm tra ràng buộc bằng cách truy cập một phần tử thông qua .at () hoặc [].
KTC

4
@seg Như vậy kiểm tra chi phí một cái gì đó. Nếu bạn viết mã chính xác, bạn không muốn trả giá đó. Phải nói rằng, tôi đã trở thành một công cụ chuyển đổi hoàn chỉnh sang phương thức std :: vector's at () mà IS đã kiểm tra. Việc sử dụng nó đã gây ra khá nhiều lỗi trong những gì tôi nghĩ là mã "chính xác".

10
Tôi tin rằng các phiên bản cũ của GCC thực sự đã ra mắt Emacs và mô phỏng Tháp Hà Nội trong đó, khi nó gặp phải một số loại hành vi không xác định. Như tôi đã nói, bất cứ điều gì cũng có thể xảy ra. ;)
jalf

4
Mọi thứ đã được nói, vì vậy điều này chỉ đảm bảo một phụ lục nhỏ. Bản dựng gỡ lỗi có thể rất dễ tha thứ trong những trường hợp này khi so sánh với bản dựng phát hành. Do thông tin gỡ lỗi được đưa vào nhị phân gỡ lỗi, ít có khả năng một cái gì đó quan trọng bị ghi đè. Đó là đôi khi lý do tại sao các bản dựng gỡ lỗi dường như hoạt động tốt trong khi bản phát hành bản dựng bị sập.
Giàu

31

Sử dụng g ++, bạn có thể thêm tùy chọn dòng lệnh : -fstack-protector-all.

Trên ví dụ của bạn, nó dẫn đến kết quả như sau:

> g++ -o t -fstack-protector-all t.cc
> ./t
3
4
/bin/bash: line 1: 15450 Segmentation fault      ./t

Nó không thực sự giúp bạn tìm hoặc giải quyết vấn đề, nhưng ít nhất thì segfault sẽ cho bạn biết rằng có điều gì đó không ổn.


10
Tôi vừa tìm thấy một lựa chọn tốt hơn: -fmudflap
Hi-Angel

1
@ Hi-Angel: Tương đương hiện đại là -fsanitize=addressbắt lỗi này cả ở thời gian biên dịch (nếu tối ưu hóa) và khi chạy.
Nate Eldredge

@NateEldredge +1, ngày nay tôi thậm chí còn sử dụng -fsanitize=undefined,address. Nhưng điều đáng chú ý là có những trường hợp góc hiếm gặp với thư viện std, khi truy cập ngoài giới hạn không được phát hiện bởi chất khử trùng . Vì lý do này, tôi khuyên bạn nên sử dụng -D_GLIBCXX_DEBUGtùy chọn bổ sung , thêm nhiều kiểm tra hơn nữa.
Hi-Angel

12

g ++ không kiểm tra giới hạn mảng và bạn có thể ghi đè lên thứ gì đó bằng 3,4 nhưng không có gì thực sự quan trọng, nếu bạn thử với số cao hơn, bạn sẽ gặp sự cố.

Bạn chỉ ghi đè lên các phần của ngăn xếp không được sử dụng, bạn có thể tiếp tục cho đến khi bạn đạt đến cuối không gian được phân bổ cho ngăn xếp và cuối cùng nó sẽ bị sập

EDIT: Bạn không có cách nào để đối phó với điều đó, có thể một bộ phân tích mã tĩnh có thể tiết lộ những lỗi đó, nhưng điều đó quá đơn giản, bạn có thể có những lỗi tương tự (nhưng phức tạp hơn) mà không bị phát hiện ngay cả đối với các máy phân tích tĩnh


6
Bạn sẽ nhận được ở đâu nếu từ đó tại địa chỉ của mảng [3] và mảng [4], "không có gì thực sự quan trọng" ??
namezero

7

Đó là hành vi không xác định theo như tôi biết. Chạy một chương trình lớn hơn với nó và nó sẽ sụp đổ ở đâu đó trên đường đi. Kiểm tra giới hạn không phải là một phần của mảng thô (hoặc thậm chí std :: vector).

Sử dụng std :: vector với std::vector::iterator's thay vì vậy bạn không phải lo lắng về nó.

Biên tập:

Chỉ để cho vui, chạy cái này và xem bao lâu cho đến khi bạn gặp sự cố:

int main()
{
   int array[1];

   for (int i = 0; i != 100000; i++)
   {
       array[i] = i;
   }

   return 0; //will be lucky to ever reach this
}

Chỉnh sửa2:

Đừng chạy nó.

Chỉnh sửa 3:

OK, đây là một bài học nhanh về mảng và mối quan hệ của chúng với con trỏ:

Khi bạn sử dụng lập chỉ mục mảng, bạn thực sự đang sử dụng một con trỏ ngụy trang (được gọi là "tham chiếu"), điều đó sẽ tự động bị hủy đăng ký. Đây là lý do tại sao thay vì * (mảng [1]), mảng [1] sẽ tự động trả về giá trị tại giá trị đó.

Khi bạn có một con trỏ tới một mảng, như thế này:

int array[5];
int *ptr = array;

Sau đó, "mảng" trong khai báo thứ hai thực sự đang phân rã thành một con trỏ đến mảng đầu tiên. Đây là hành vi tương đương với điều này:

int *ptr = &array[0];

Khi bạn cố gắng truy cập ngoài những gì bạn đã phân bổ, bạn thực sự chỉ sử dụng một con trỏ đến bộ nhớ khác (điều mà C ++ sẽ không phàn nàn). Lấy chương trình ví dụ của tôi ở trên, tương đương với điều này:

int main()
{
   int array[1];
   int *ptr = array;

   for (int i = 0; i != 100000; i++, ptr++)
   {
       *ptr++ = i;
   }

   return 0; //will be lucky to ever reach this
}

Trình biên dịch sẽ không phàn nàn vì trong lập trình, bạn thường phải giao tiếp với các chương trình khác, đặc biệt là hệ điều hành. Điều này được thực hiện với con trỏ khá một chút.


3
Tôi nghĩ rằng bạn đã quên tăng "ptr" trong ví dụ cuối cùng của bạn ở đó. Bạn đã vô tình tạo ra một số mã được xác định rõ.
Jeff Lake

1
Haha, xem tại sao bạn không nên sử dụng mảng thô?
lừa

"Đây là lý do tại sao thay vì * (mảng [1]), mảng [1] sẽ tự động trả về giá trị ở giá trị đó." Bạn có chắc chắn * (mảng [1]) sẽ hoạt động đúng không? Tôi nghĩ rằng nó phải là * (mảng + 1). ps: Lol, nó giống như gửi một tin nhắn đến quá khứ. Nhưng, dù sao đi nữa:
muyustan

5

Dấu

Nếu bạn muốn có các mảng kích thước ràng buộc nhanh với kiểm tra lỗi phạm vi, hãy thử sử dụng boost :: mảng , (cũng là mảng std :: tr1 :: từ <tr1/array>đó sẽ là vùng chứa chuẩn trong thông số kỹ thuật C ++ tiếp theo). Nó nhanh hơn nhiều so với std :: vector. Nó dự trữ bộ nhớ trên heap hoặc bên trong lớp thể hiện, giống như mảng int [].
Đây là mã mẫu đơn giản:

#include <iostream>
#include <boost/array.hpp>
int main()
{
    boost::array<int,2> array;
    array.at(0) = 1; // checking index is inside range
    array[1] = 2;    // no error check, as fast as int array[2];
    try
    {
       // index is inside range
       std::cout << "array.at(0) = " << array.at(0) << std::endl;

       // index is outside range, throwing exception
       std::cout << "array.at(2) = " << array.at(2) << std::endl; 

       // never comes here
       std::cout << "array.at(1) = " << array.at(1) << std::endl;  
    }
    catch(const std::out_of_range& r)
    {
        std::cout << "Something goes wrong: " << r.what() << std::endl;
    }
    return 0;
}

Chương trình này sẽ in:

array.at(0) = 1
Something goes wrong: array<>: index out of range

4

C hoặc C ++ sẽ không kiểm tra giới hạn của truy cập mảng.

Bạn đang phân bổ mảng trên ngăn xếp. Lập chỉ mục mảng thông qua array[3]tương đương với *(array + 3) , trong đó mảng là con trỏ tới & mảng [0]. Điều này sẽ dẫn đến hành vi không xác định.

Một cách để bắt gặp điều này đôi khi trong C là sử dụng trình kiểm tra tĩnh, chẳng hạn như nẹp . Nếu bạn chạy:

splint +bounds array.c

trên,

int main(void)
{
    int array[1];

    array[1] = 1;

    return 0;
}

sau đó bạn sẽ nhận được cảnh báo:

mảng.c: (trong hàm chính) .c: 5: 9)> = 1 Một bộ nhớ ghi có thể ghi vào một địa chỉ nằm ngoài bộ đệm được phân bổ.


Sửa chữa: nó đã được HĐH hoặc chương trình khác phân bổ. Ông đang ghi đè lên bộ nhớ khác.
lừa

1
Nói rằng "C / C ++ sẽ không kiểm tra giới hạn" không hoàn toàn chính xác - không có gì ngăn cản việc thực hiện tuân thủ cụ thể làm như vậy, theo mặc định hoặc với một số cờ biên dịch. Chỉ là không ai trong số họ bận tâm.
Pavel Minaev

3

Bạn chắc chắn ghi đè lên ngăn xếp của bạn, nhưng chương trình đủ đơn giản để các hiệu ứng này không được chú ý.


2
Việc ngăn xếp có bị ghi đè hay không phụ thuộc vào nền tảng.
Chris Cleeland

3

Chạy nó thông qua Valgrind và bạn có thể thấy một lỗi.

Như Falaina đã chỉ ra, valgrind không phát hiện ra nhiều trường hợp tham nhũng stack. Tôi vừa thử mẫu theo valgrind, và nó thực sự báo cáo không có lỗi. Tuy nhiên, Valgrind có thể là công cụ tìm kiếm nhiều loại vấn đề bộ nhớ khác, nó chỉ không đặc biệt hữu ích trong trường hợp này trừ khi bạn sửa đổi tính cồng kềnh của mình để bao gồm tùy chọn --stack-check. Nếu bạn xây dựng và chạy mẫu như

g++ --stack-check -W -Wall errorRange.cpp -o errorRange
valgrind ./errorRange

valgrind sẽ báo lỗi.


2
Trên thực tế, Valgrind khá kém trong việc xác định truy cập mảng không chính xác trên ngăn xếp. (và đúng như vậy, điều tốt nhất có thể làm là đánh dấu toàn bộ ngăn xếp là vị trí ghi hợp lệ)
Falaina

@Falaina - điểm tốt, nhưng Valgrind có thể phát hiện ít nhất một số lỗi ngăn xếp.
Stout Stout

Và valgrind sẽ không thấy có gì sai với mã bởi vì trình biên dịch đủ thông minh để tối ưu hóa mảng và chỉ cần xuất ra một chữ 3 và 4. Việc tối ưu hóa đó xảy ra trước khi gcc kiểm tra giới hạn mảng, đó là lý do tại sao gcc cảnh báo ngoài giới hạn đã không được hiển thị.
Goswin von Brederlow

2

Hành vi không xác định làm việc có lợi cho bạn. Bất cứ bộ nhớ nào bạn đang ghi đè rõ ràng là không có gì quan trọng. Lưu ý rằng C và C ++ không kiểm tra giới hạn trên các mảng, vì vậy những thứ như thế sẽ không bị bắt khi biên dịch hoặc thời gian chạy.


5
Không, hành vi không xác định "hoạt động có lợi cho bạn" khi nó bị hỏng hoàn toàn. Khi nó xuất hiện để làm việc, đó là về kịch bản tồi tệ nhất có thể.
jalf

@JohnBode: Sau đó, sẽ tốt hơn nếu bạn sửa từ ngữ theo nhận xét của jalf
Destructor

1

Khi bạn khởi tạo mảng với int array[2], không gian cho 2 số nguyên được phân bổ; nhưng định danh arraychỉ đơn giản là chỉ đến điểm bắt đầu của không gian đó. Khi bạn truy cập array[3]array[4], trình biên dịch sau đó chỉ cần tăng địa chỉ đó để trỏ đến nơi các giá trị đó sẽ là, nếu mảng đủ dài; hãy thử truy cập một cái gì đó như array[42]mà không khởi tạo nó trước, cuối cùng bạn sẽ nhận được bất kỳ giá trị nào đã xảy ra trong bộ nhớ tại vị trí đó.

Biên tập:

Thông tin thêm về con trỏ / mảng: http://home.netcom.com/~tjensen/ptr/pointers.htm


0

khi bạn khai báo mảng int [2]; bạn dự trữ 2 không gian bộ nhớ mỗi 4 byte (chương trình 32 bit). nếu bạn gõ mảng [4] trong mã của mình, nó vẫn tương ứng với một cuộc gọi hợp lệ nhưng chỉ trong thời gian chạy, nó sẽ đưa ra một ngoại lệ chưa được xử lý. C ++ sử dụng quản lý bộ nhớ thủ công. Đây thực sự là một lỗ hổng bảo mật được sử dụng cho các chương trình hack

điều này có thể giúp hiểu:

int * somepulum;

somepulum [0] = somepulum [5];


0

Theo tôi hiểu, các biến cục bộ được phân bổ trên ngăn xếp, do đó, vượt quá giới hạn trên ngăn xếp của riêng bạn chỉ có thể ghi đè lên một số biến cục bộ khác, trừ khi bạn đi quá nhiều và vượt quá kích thước ngăn xếp của bạn. Vì bạn không có biến nào khác được khai báo trong hàm của mình - nó không gây ra bất kỳ tác dụng phụ nào. Hãy thử khai báo một biến / mảng khác ngay sau biến đầu tiên của bạn và xem điều gì sẽ xảy ra với nó.


0

Khi bạn viết 'mảng [index]' bằng C, nó sẽ dịch nó sang hướng dẫn máy.

Bản dịch là một cái gì đó như:

  1. 'lấy địa chỉ của mảng'
  2. 'lấy kích thước của kiểu mảng đối tượng được tạo thành từ'
  3. 'nhân kích thước của loại theo chỉ mục'
  4. 'thêm kết quả vào địa chỉ của mảng'
  5. 'đọc những gì tại địa chỉ kết quả'

Kết quả giải quyết một cái gì đó có thể hoặc không thể là một phần của mảng. Để đổi lấy tốc độ nhanh của hướng dẫn máy, bạn sẽ mất mạng lưới an toàn của máy tính kiểm tra mọi thứ cho bạn. Nếu bạn tỉ mỉ và cẩn thận thì đó không phải là vấn đề. Nếu bạn cẩu thả hoặc mắc lỗi, bạn sẽ bị cháy. Đôi khi nó có thể tạo ra một lệnh không hợp lệ gây ra ngoại lệ, đôi khi không.


0

Một cách tiếp cận hay mà tôi đã thấy thường xuyên và tôi đã thực sự được sử dụng là tiêm một số phần tử kiểu NULL (hoặc một phần tử được tạo, như uint THIS_IS_INFINITY = 82862863263; ) vào cuối mảng.

Sau đó, tại kiểm tra điều kiện vòng lặp, TYPE *pagesWordslà một số loại mảng con trỏ:

int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]);

realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1);

pagesWords[pagesWordsLength] = MY_NULL;

for (uint i = 0; i < 1000; i++)
{
  if (pagesWords[i] == MY_NULL)
  {
    break;
  }
}

Giải pháp này sẽ không từ nếu mảng chứa đầy các structloại.


0

Như đã đề cập trong câu hỏi sử dụng std :: vector :: at sẽ giải quyết vấn đề và thực hiện kiểm tra ràng buộc trước khi truy cập.

Nếu bạn cần một mảng kích thước không đổi nằm trên ngăn xếp như mã đầu tiên của bạn, hãy sử dụng mảng std :: container mới của C ++ 11; như vector có std :: mảng :: tại hàm. Trong thực tế, hàm tồn tại trong tất cả các thùng chứa tiêu chuẩn, trong đó nó có nghĩa, trong đó toán tử [] được định nghĩa :( deque, map, unordered_map) ngoại trừ std :: bitset trong đó nó được gọi là std :: bitset: :kiểm tra.


0

libstdc ++, một phần của gcc, có chế độ gỡ lỗi đặc biệt để kiểm tra lỗi. Nó được kích hoạt bởi cờ biên dịch -D_GLIBCXX_DEBUG. Trong số những thứ khác, nó kiểm tra giới hạn với std::vectorchi phí hiệu suất. Đây là bản demo trực tuyến với phiên bản gần đây của gcc.

Vì vậy, thực sự bạn có thể thực hiện kiểm tra giới hạn với chế độ gỡ lỗi libstdc ++ nhưng bạn chỉ nên thực hiện khi kiểm tra vì chi phí hiệu năng đáng chú ý so với chế độ libstdc ++ bình thường.


0

Nếu bạn thay đổi chương trình một chút:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    INT NOTHING;
    CHAR FOO[4];
    STRCPY(FOO, "BAR");
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    COUT << FOO << ENDL;
    return 0;
}

(Thay đổi về chữ viết hoa - đặt những chữ in thường nếu bạn muốn thử.)

Bạn sẽ thấy rằng biến foo đã bị vứt đi. Mã của bạn sẽ lưu trữ các giá trị vào mảng không tồn tại [3] và mảng [4] và có thể truy xuất chúng một cách chính xác, nhưng dung lượng lưu trữ thực tế được sử dụng sẽ từ foo .

Vì vậy, bạn có thể "thoát khỏi" với việc vượt quá giới hạn của mảng trong ví dụ ban đầu của mình, nhưng với chi phí gây ra thiệt hại ở nơi khác - thiệt hại có thể chứng minh là rất khó chẩn đoán.

Về lý do tại sao không có kiểm tra giới hạn tự động - một chương trình được viết chính xác không cần đến nó. Một khi điều đó đã được thực hiện, không có lý do gì để kiểm tra giới hạn thời gian chạy và làm như vậy sẽ chỉ làm chậm chương trình. Tốt nhất để có được tất cả đã tìm ra trong quá trình thiết kế và mã hóa.

C ++ dựa trên C, được thiết kế gần với ngôn ngữ lắp ráp nhất có thể.

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.