Sức mạnh tính toán tối đa của việc thực hiện C


28

Nếu chúng ta đi theo cuốn sách (hoặc bất kỳ phiên bản nào khác của đặc tả ngôn ngữ nếu bạn thích), thì việc triển khai C có thể có bao nhiêu sức mạnh tính toán?

Lưu ý rằng việc triển khai của C C có một ý nghĩa kỹ thuật: đó là một khởi tạo cụ thể của đặc tả ngôn ngữ lập trình C trong đó hành vi được xác định thực hiện được ghi lại. Việc thực hiện AC không cần phải chạy trên máy tính thực tế. Nó phải thực hiện toàn bộ ngôn ngữ, bao gồm mọi đối tượng có biểu diễn chuỗi bit và các loại có kích thước do xác định thực hiện.

Đối với mục đích của câu hỏi này, không có lưu trữ bên ngoài. Đầu vào / đầu ra duy nhất bạn có thể thực hiện là getchar(để đọc đầu vào chương trình) và putchar(để ghi đầu ra chương trình). Ngoài ra, bất kỳ chương trình nào gọi hành vi không xác định là không hợp lệ: một chương trình hợp lệ phải có hành vi được xác định bởi đặc tả C cộng với mô tả thực hiện các hành vi được xác định thực hiện được liệt kê trong phụ lục J (cho C99). Lưu ý rằng việc gọi các chức năng thư viện không được đề cập trong tiêu chuẩn là hành vi không xác định.

Phản ứng ban đầu của tôi là việc triển khai C không hơn gì một máy tự động hữu hạn, bởi vì nó có giới hạn về dung lượng bộ nhớ địa chỉ (bạn không thể giải quyết nhiều hơn sizeof(char*) * CHAR_BITcác bit lưu trữ, vì các địa chỉ bộ nhớ riêng biệt phải có các mẫu bit riêng biệt khi được lưu trữ trong một con trỏ byte).

Tuy nhiên tôi nghĩ rằng một triển khai có thể làm nhiều hơn thế. Theo như tôi có thể nói, tiêu chuẩn áp đặt không giới hạn về độ sâu của đệ quy. Vì vậy, bạn có thể thực hiện bao nhiêu cuộc gọi hàm đệ quy tùy thích, chỉ tất cả trừ một số lượng cuộc gọi hữu hạn phải sử dụng các registerđối số không thể định địa chỉ ( ). Do đó, việc thực hiện C cho phép đệ quy tùy ý và không có giới hạn về số lượng registerđối tượng có thể mã hóa automata đẩy xuống xác định.

Điều này có đúng không? Bạn có thể tìm thấy một triển khai C mạnh mẽ hơn? Có thực hiện Turing-Complete C không?


4
@Dave: Như Gilles đã giải thích, có vẻ như bạn có thể có bộ nhớ không giới hạn, nhưng không có cách nào để trực tiếp giải quyết nó.
Jukka Suomela

2
Từ lời giải thích của bạn, có vẻ như bất kỳ triển khai C nào cũng chỉ có thể được lập trình để chấp nhận các ngôn ngữ được chấp nhận bởi automata đẩy xuống xác định , yếu hơn các ngôn ngữ thậm chí không có ngữ cảnh. Tuy nhiên, quan sát này ít được quan tâm theo ý kiến ​​của tôi, vì câu hỏi là một ứng dụng sai của tiệm cận.
Warren Schudy

3
Một điểm cần lưu ý là có nhiều cách để kích hoạt "hành vi được xác định theo thực hiện" (hoặc "hành vi không xác định"). Và nói chung, việc triển khai có thể cung cấp, ví dụ, các hàm thư viện cung cấp chức năng không được xác định trong tiêu chuẩn C. Tất cả những thứ này cung cấp "sơ hở" mà qua đó bạn có thể truy cập, giả sử, một máy hoàn chỉnh Turing. Hoặc thậm chí một cái gì đó mạnh mẽ hơn nhiều, như một lời sấm giải quyết vấn đề tạm dừng. Một ví dụ ngu ngốc: hành vi được xác định theo thực hiện của tràn số nguyên đã ký hoặc chuyển đổi con trỏ số nguyên có thể cho phép bạn truy cập vào một lời tiên tri như vậy.
Jukka Suomela

7
Nhân tiện, có thể nên thêm thẻ "giải trí" (hoặc bất cứ điều gì chúng tôi đang sử dụng cho các câu đố vui) để mọi người không quá coi trọng điều này. Đó rõ ràng là "câu hỏi sai" để hỏi, nhưng tuy nhiên tôi thấy nó thú vị và hấp dẫn. :)
Jukka Suomela

2
@Jukka: Ý kiến ​​hay đấy. Ví dụ, tràn X = ghi X / 3 trên băng và di chuyển theo hướng X% 3, underflow = kích hoạt tín hiệu tương ứng với ký hiệu trên băng. Nó cảm thấy giống như một sự lạm dụng, nhưng nó chắc chắn theo tinh thần của câu hỏi của tôi. Bạn có thể viết nó như một câu trả lời? (@others: Không phải tôi muốn làm nản lòng những lời đề nghị thông minh như vậy!)
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


8

Như đã lưu ý trong câu hỏi, tiêu chuẩn C yêu cầu tồn tại một giá trị UCHAR_MAX sao cho mọi biến loại unsigned charsẽ luôn giữ một giá trị trong khoảng từ 0 đến UCHAR_MAX. Nó đòi hỏi thêm rằng mọi đối tượng được phân bổ động phải được biểu diễn bằng một chuỗi byte có thể nhận dạng thông qua con trỏ loại unsigned char*và có một hằng số sizeof(unsigned char*)sao cho mọi con trỏ của loại đó có thể được xác định bởi một chuỗi các sizeof(unsigned char *)giá trị loại unsigned char. Do đó, số lượng đối tượng có thể được phân bổ động đồng thời bị giới hạn cứng đối với 10 10 đối tượng, nhưng từ góc độ lý thuyết, sự tồn tại của bất kỳ ràng buộc nào, dù lớn đến đâu, có nghĩa là một cái gì đó không phải là vô hạn. . Không có gì ngăn cản trình biên dịch lý thuyết gán các giá trị của các hằng số đó để hỗ trợ hơn 10UCHAR_MAXsizeof(unsigned char)101010

Một chương trình có thể lưu trữ một lượng thông tin không giới hạn trên ngăn xếp nếu không có gì được phân bổ trên ngăn xếp bao giờ có địa chỉ của nó ; do đó, người ta có thể có một chương trình C có khả năng thực hiện một số điều mà không thể thực hiện được bởi bất kỳ máy tự động hữu hạn nào ở bất kỳ kích thước nào. Do đó, mặc dù (hoặc có lẽ vì) quyền truy cập vào các biến ngăn xếp bị hạn chế hơn nhiều so với quyền truy cập vào các biến được phân bổ động, nhưng nó biến C từ một máy tự động hữu hạn thành máy tự động đẩy xuống.

Tuy nhiên, có một nếp nhăn tiềm năng khác: yêu cầu rằng nếu một chương trình kiểm tra các chuỗi có độ dài cố định cơ bản của các giá trị ký tự được liên kết với hai con trỏ tới các đối tượng khác nhau, thì các chuỗi đó phải là duy nhất. Bởi vì chỉ có UCHAR_MAXsizeof(unsigned char)các chuỗi có thể có của các giá trị ký tự, bất kỳ chương trình nào tạo ra một số con trỏ tới các đối tượng riêng biệt vượt quá mức đó không thể tuân thủ tiêu chuẩn C rất chậm], sau đó thực sự có thể biến C thành ngôn ngữ hoàn chỉnh.nếu mã đã từng kiểm tra chuỗi ký tự được liên kết với các con trỏ đó . Tuy nhiên, trong một số trường hợp, trình biên dịch có thể xác định rằng không có mã nào sẽ kiểm tra chuỗi ký tự được liên kết với một con trỏ. Nếu mỗi "char" thực sự có khả năng chứa bất kỳ số nguyên hữu hạn nào và bộ nhớ của máy là một chuỗi số nguyên vô hạn [được cung cấp một máy Turing băng không giới hạn, thì người ta có thể mô phỏng một máy như vậy mặc dù nó sẽ là


Với một chiếc máy như vậy, sizeof (char) sẽ trả về cái gì?
TLW

1
@TLW: Giống như mọi máy khác: 1. Mặc dù vậy, các macro CHAR_BITS và CHAR_MAX sẽ có vấn đề hơn một chút; Tiêu chuẩn sẽ không cho phép khái niệm các loại không có giới hạn.
supercat

Rất tiếc, ý tôi là CHAR_BITS, như bạn đã nói, xin lỗi.
TLW

7

Với thư viện phân luồng (tùy chọn) của C11, có thể thực hiện triển khai Turing hoàn chỉnh với độ sâu đệ quy không giới hạn.

Tạo một chủ đề mới mang lại một ngăn xếp thứ hai; hai ngăn xếp là đủ cho Turing hoàn chỉnh. Một ngăn xếp đại diện cho những gì ở bên trái của đầu, ngăn xếp khác những gì ở bên phải.


Nhưng các máy Turing với một cuộn băng tiến triển vô hạn chỉ theo một hướng cũng mạnh mẽ như các máy Turing với một băng tiến vô hạn theo hai hướng. Ngoài ra, nhiều luồng có thể được mô phỏng bởi một bộ lập lịch. Dù sao, chúng tôi thậm chí không yêu cầu một thư viện luồng.
xamid

3

Tôi nghĩ rằng nó đã hoàn thành : chúng ta có thể viết chương trình mô phỏng UTM bằng thủ thuật này (tôi đã nhanh chóng viết mã bằng tay để có thể có một số lỗi cú pháp ... nhưng tôi hy vọng không có lỗi (chính) nào trong logic :-)

  • định nghĩa một cấu trúc có thể được sử dụng như một danh sách liên kết kép để biểu diễn băng
    typdef struct {
      cell_t * trước; // ô bên trái
      cell_t * succ; // ô bên phải
      int val; // giá trị ô
    } cell_t 

headsẽ là một con trỏ tới một cell_tcấu trúc

  • xác định cấu trúc có thể được sử dụng để lưu trữ trạng thái hiện tại và cờ
    typedef struct {
      int nhà nước;
      cờ int;
    } thông tin 
  • sau đó xác định hàm vòng lặp đơn mô phỏng Universal TM khi phần đầu nằm giữa ranh giới của danh sách liên kết kép; khi đầu chạm vào một ranh giới, đặt cờ của cấu trúc info_t (HIT_LEFT, HIT_RIGHT) và trả về:
void simulation_UTM (cell_t * head, info_t * information) {
  trong khi (đúng) {
    head-> val = UTM_nextsymbol [thông tin-> trạng thái, head-> val]; // viết ký hiệu
    thông tin-> bang = UTM_nextstate [thông tin-> trạng thái, đầu-> val]; // trạng thái tiếp theo
    if (thông tin-> state == HALT_STATE) {// in nếu chấp nhận và thoát khỏi chương trình
       putchar ((thông tin-> bang == ACCEPT_STATE)? '1': '0');
       thoát (0);
    }
    int move = UTM_nextmove [thông tin-> trạng thái, đầu-> val];
    if (di chuyển == MOVE_LEFT) {
      đầu = đầu-> trước; // di chuyển sang trái
      if (head == NULL) {thông tin-> cờ = HIT_LEFT; trở về; }
    } khác {
      đầu = đầu-> succ; // đi sang phải
      if (head == NULL) {thông tin-> cờ = HIT_RIGHT; trở về; }
    }
  } // vẫn còn trong ranh giới ... tiếp tục
}
  • sau đó xác định hàm đệ quy trước tiên gọi thủ tục UTM mô phỏng và sau đó gọi đệ quy chính nó khi băng cần được mở rộng; khi băng cần được mở rộng ở trên cùng (HIT_RIGHT) không có vấn đề gì, khi băng cần được dịch ở phía dưới (HIT_LEFT) chỉ cần dịch chuyển các giá trị của các ô bằng danh sách được liên kết kép:
void stacker (cell_t * top, cell_t * bottom, cell_t * head, info_t * information) {
  mô phỏng_UTM (đầu, thông tin);
  cell_t newcell; // ô mới
  newcell.pred = top; // cập nhật danh sách liên kết kép với ô mới
  newcell.succ = NULL;
  đầu trang-> succ = & newcell;
  newcell.val = EMPTY_SYMBOL;

  chuyển đổi (thông tin-> nhấn) {
    trường hợp HIT_RIGHT:
      stacker (& newcell, bottom, newcell, thông tin);
      phá vỡ;
    trường hợp HIT_BOTTOM:
      cell_t * tmp = newcell;
      while (tmp-> pre! = NULL) {// thay đổi giá trị
        tmp-> val = tmp-> pre-> val;
        tmp = tmp-> trước;
      }
      tmp-> val = EMPTY_SYMBOL;
      stacker (& newcell, đáy, đáy, thông tin);
      phá vỡ;
  }
}
  • băng ban đầu có thể được lấp đầy bằng một hàm đệ quy đơn giản, xây dựng danh sách liên kết đôi và sau đó gọi stackerhàm khi nó đọc ký hiệu cuối cùng của băng đầu vào (sử dụng hàm đọc)
void init_tape (cell_t * top, cell_t * bottom, info_t * thông tin) {
  cell_t newcell;
  int c = readar ();
  ngăn xếp if (c == END_OF_INPUT) (& top, bottom, bottom, information); // không còn ký hiệu, bắt đầu
  newcell.pred = top;
  if (top! = NULL) top.succ = & newcell; đáy khác = & newcell;
  init_tape (& newcell, dưới cùng, thông tin);
}

EDIT: sau khi suy nghĩ một chút về nó, có một vấn đề với con trỏ ...

nếu mọi cuộc gọi của hàm đệ quy stackercó thể tạo ra một con trỏ hợp lệ cho một biến được định nghĩa cục bộ trong trình gọi thì mọi thứ đều ổn ; mặt khác, thuật toán của tôi không thể xác định danh sách liên kết đôi hợp lệ trên đệ quy không giới hạn (và trong trường hợp này, không thấy cách sử dụng đệ quy để mô phỏng bộ lưu trữ truy cập ngẫu nhiên không giới hạn).


3
stackernewcellstacker2n/sns= =sizeof(cell_t)

@Gilles: bạn nói đúng (xem phần chỉnh sửa của tôi); nếu bạn giới hạn độ sâu đệ quy, bạn sẽ có một máy tự động hữu hạn
Marzio De Biasi

@MarzioDeBiasi Không, anh ấy đã sai vì anh ấy đề cập đến một triển khai cụ thể mà tiêu chuẩn không giả định trước. Trong thực tế, không có giới hạn lý thuyết đến độ sâu đệ quy trong C . Một lựa chọn sử dụng triển khai dựa trên ngăn xếp giới hạn không nói lên điều gì về giới hạn lý thuyết của ngôn ngữ. Nhưng Turing-đầy đủ là một giới hạn lý thuyết.
xamid

0

Miễn là bạn có kích thước ngăn xếp cuộc gọi không giới hạn, bạn có thể mã hóa băng của mình trên ngăn xếp cuộc gọi và truy cập ngẫu nhiên bằng cách tua lại con trỏ ngăn xếp mà không quay lại từ các lệnh gọi hàm.

EdIT : Nếu bạn chỉ có thể sử dụng ram, là hữu hạn, công trình này không hoạt động nữa, vì vậy hãy xem bên dưới.

Tuy nhiên, rất có vấn đề tại sao ngăn xếp của bạn có thể là vô hạn nhưng ram nội tại thì không. Vì vậy, thực sự tôi sẽ nói rằng bạn thậm chí không thể nhận ra tất cả các ngôn ngữ thông thường, vì số lượng trạng thái bị giới hạn (nếu bạn không tính thủ thuật tua lại ngăn xếp để khai thác ngăn xếp vô hạn).

Tôi thậm chí sẽ suy đoán rằng số lượng ngôn ngữ bạn có thể nhận ra là hữu hạn (ngay cả khi bản thân các ngôn ngữ có thể là vô hạn, ví dụ như a*không sao, nhưng b^kchỉ hoạt động với số lượng hữu hạn k).

EDIT : Điều này không đúng, vì bạn có thể mã hóa trạng thái hiện tại trong các chức năng bổ sung, vì vậy bạn có thể thực sự nhận ra TẤT CẢ các ngôn ngữ thông thường.

Rất có thể bạn có thể nhận được tất cả các ngôn ngữ Loại 2 vì cùng một lý do, nhưng tôi không chắc liệu bạn có thể quản lý để đặt cả hai, trạng thái không ngăn xếp ngăn xếp vào ngăn xếp cuộc gọi hay không. Nhưng trên một lưu ý chung, bạn có thể quên ram một cách hiệu quả, vì bạn luôn có thể chia tỷ lệ kích thước của máy tự động để bảng chữ cái của bạn vượt quá khả năng của ram. Vì vậy, nếu bạn có thể mô phỏng một TM chỉ với một ngăn xếp, Loại 2 sẽ bằng Loại 0, phải không?


5
Một ngăn xếp-con trỏ xếp chồng là gì? (Lưu ý rằng từ ngăn xếp xếp chồng không xuất hiện trong tiêu chuẩn C.) Câu hỏi của tôi là về C như một lớp ngôn ngữ chính thức, không phải về triển khai C trên máy tính (rõ ràng là máy trạng thái hữu hạn). Nếu bạn muốn truy cập ngăn xếp cuộc gọi, bạn phải thực hiện theo cách được cung cấp bởi ngôn ngữ. Ví dụ: bằng cách lấy địa chỉ của các đối số hàm - nhưng bất kỳ triển khai đã cho nào chỉ có số lượng địa chỉ hữu hạn, sau đó giới hạn độ sâu của đệ quy.
Gilles 'SO- ngừng trở nên xấu xa'

Tôi đã sửa đổi câu trả lời của mình để loại trừ việc sử dụng con trỏ ngăn xếp.
bitmask

1
Tôi không hiểu bạn đang đi đâu với câu trả lời sửa đổi của bạn (ngoài việc thay đổi công thức từ các chức năng tính toán sang ngôn ngữ được công nhận). Vì các hàm cũng có một địa chỉ, nên bạn cần thực hiện đủ lớn để thực hiện bất kỳ máy trạng thái hữu hạn nào. Câu hỏi đặt ra là liệu và cách thức triển khai C có thể làm được nhiều hơn (giả sử, thực hiện một máy Turing phổ dụng) mà không cần dựa vào hành vi không xác định.
Gilles 'SO- ngừng trở nên xấu xa'

0

Tôi đã nghĩ về điều này một lần và quyết định thử thực hiện một ngôn ngữ phi ngữ cảnh bằng cách sử dụng ngữ nghĩa dự kiến; phần quan trọng của việc thực hiện là chức năng sau:

void *it;

void read_triple(void *back)
{
  if(read_a()) read_triple(&back);
  else reject();
  for(it = back; it != NULL; it = *it)
     if(!read_b()) reject();
  if(read_c()) return;
  else reject();
}

{anbncn}

Ít nhất, tôi nghĩ rằng điều này làm việc. Có thể là tôi đang mắc một số sai lầm cơ bản.

Một phiên bản cố định:

void *it;

void read_triple(void *back)
{
  if(read_a()) read_triple(&back);
  else for(it = back; it != NULL; it = * (void **) it)
     if(!read_b()) reject();
  if(read_c()) return;
  else reject();
}

Vâng, không phải là một sai lầm cơ bản, nhưng it = *itnên được thay thế bằng it = * (void **) it, vì nếu không thì *itlà loại void.
Ben Standeven

Tôi sẽ rất ngạc nhiên nếu đi du lịch ngăn xếp cuộc gọi như thế sẽ được xác định hành vi trong C.
Radu GRIGore

Ồ, điều này sẽ không hoạt động, vì 'b' đầu tiên khiến read_a () không thành công và do đó gây ra sự từ chối.
Ben Standeven

Nhưng việc di chuyển ngăn xếp cuộc gọi theo cách này là hợp pháp, vì tiêu chuẩn C nói: "Đối với một đối tượng như vậy [tức là một đối tượng có bộ nhớ tự động] không có kiểu mảng có chiều dài thay đổi, thời gian tồn tại của nó kéo dài từ việc xâm nhập vào khối với mà nó được liên kết cho đến khi việc thực thi khối đó kết thúc theo bất kỳ cách nào. (Nhập một khối đính kèm hoặc gọi một hàm tạm dừng, nhưng không kết thúc, thực thi khối hiện tại.) Nếu khối được nhập theo cách đệ quy, một thể hiện mới của đối tượng được tạo ra mỗi lần. " Vì vậy, mỗi lệnh gọi của read_triple sẽ tạo ra một con trỏ mới có thể được sử dụng trong đệ quy.
Ben Standeven

2
@BenStandeven Vấn đề là, nếu bạn làm điều đó, cuối cùng bạn sẽ hết địa chỉ. Nếu bạn tạo một đối tượng có thể đánh địa chỉ tại mỗi cuộc gọi đệ quy, thì có giới hạn về độ sâu đệ quy (không quá2CHAR_BITsizeof (char *).
Gilles 'SO- ngừng trở nên xấu xa'

0

Dọc theo dòng câu trả lời của @ supercat:

Các tuyên bố về tính không hoàn chỉnh của C dường như tập trung vào các đối tượng riêng biệt đó nên có các địa chỉ riêng biệt và tập hợp các địa chỉ được coi là hữu hạn. Như @supercat viết

Như đã lưu ý trong câu hỏi, tiêu chuẩn C yêu cầu tồn tại một giá trị UCHAR_MAXsao cho mọi biến kiểu char không dấu sẽ luôn giữ một giá trị trong khoảng từ 0 đến UCHAR_MAX, bao gồm. Nó đòi hỏi thêm rằng mọi đối tượng được phân bổ động phải được biểu diễn bằng một chuỗi byte có thể nhận dạng được thông qua con trỏ của kiểu không dấu char * và có một hằng số sizeof(unsigned char*)sao cho mọi con trỏ của loại đó được nhận dạng bởi một chuỗisizeof(unsigned char *) giá trị loại không dấu char

Tôi tự hỏi nếu C cho phép chúng ta vận hành các chuỗi và chuỗi byte vô hạn. Nếu vậy, chúng ta có thể lấy tập hợp unsigned char*con trỏ làN và tập hợp các ký tự không dấu {0,1}chỉ để đơn giản. Sau đó, chúng ta nên xác định sizeof(unsigned char*), st cardinality bộ{0,1}kích thước(bạnnStôigned chmộtr*) không ít hơn cardinality của N. Bằng cách này, với mọi con trỏ, chúng ta có thể gán một chuỗi các ký tự không dấu có độ dài phù hợp. Điều này hoạt động nếu được xác định sizeof(unsigned char*)N (ω nếu bạn ước).

Tại thời điểm này, người ta nên kiểm tra rằng tiêu chuẩn C thực sự sẽ cho phép điều đó.

Một vấn đề nhỏ trong việc xây dựng này là không phải mọi chuỗi ký tự không dấu đều biểu thị một con trỏ. Điều này có thể hoặc có thể không tốt, nhưng có thể được sửa chữa nếu cần. Nhưng cũng có thể là tiêu chuẩn sẽ "vô tình" yêu cầu các biểu diễn byte của các đối tượng là hữu hạn hoặc một số số là số nguyên (giả sử, kết quả sizeofluôn luôn làZ) và sau đó chúng ta có thể hết may mắn.


1
Nhiều thao tác trên các loại tích phân được xác định để có một kết quả là Modulo giảm một lần so với giá trị tối đa có thể biểu thị trong loại kết quả. Làm thế nào điều đó sẽ làm việc nếu tối đa đó là một thứ tự không hữu hạn?
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles Đây là một điểm thú vị. Thực sự không rõ ràng những gì sẽ là ngữ nghĩa của uintptr_t p = (uintptr_t)sizeof(void*)(đưa \ omega vào một cái gì đó chứa các số nguyên không dấu). Tôi không biết. Chúng tôi có thể thoát khỏi việc xác định kết quả là 0 (hoặc bất kỳ số nào khác).
Alexey B.

1
uintptr_tsẽ phải là vô hạn quá. Xin lưu ý bạn, loại này là tùy chọn - nhưng nếu bạn có vô số giá trị con trỏ riêng biệt thì sizeof(void*)cũng phải là vô hạn, do đó size_tphải là vô hạn. Tuy nhiên, sự phản đối của tôi về modulo giảm không quá rõ ràng - nó chỉ phát huy tác dụng nếu có tràn, nhưng nếu bạn cho phép các loại vô hạn thì chúng có thể không bao giờ tràn. Nhưng trên bàn tay nắm bắt, mỗi loại có một giá trị tối thiểu và tối đa, theo như tôi có thể nói ngụ ý rằng UINT_MAX+1phải tràn.
Gilles 'SO- ngừng trở nên xấu xa'

Cũng là một điểm tốt. Thật vậy, chúng ta có một loạt các loại (con trỏ và size_t) nên là ℕ, hoặc một số cấu trúc dựa trên chúng (đối với size_t nếu sẽ là một cái gì đó như ∪ {ω}). Bây giờ, nếu đối với một số loại này, tiêu chuẩn yêu cầu macro xác định giá trị tối đa (PTR_MAX hoặc đại loại như thế), mọi thứ sẽ có nhiều lông. Nhưng cho đến nay tôi chỉ có thể tài trợ cho yêu cầu macro MIN / MAX cho các loại không phải con trỏ.
Alexey B.

Một khả năng khác để điều tra là xác định cả hai size_tvà loại con trỏ là ∪ {ω}. Điều này được loại bỏ các vấn đề tối thiểu / tối đa. Vấn đề với ngữ nghĩa tràn vẫn còn. Những gì nên được ngữ nghĩa uint x = (uint)ωlà không rõ ràng với tôi. Một lần nữa, chúng ta có thể mất 0, nhưng trông nó hơi xấu.
Alexey B.
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.