Con trỏ C để khai báo mảng với bitwise và toán tử


9

Tôi muốn hiểu đoạn mã sau:

//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
    return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}

Nó bắt nguồn từ tập tin ctype.h từ mã nguồn của hệ điều hành obenbsd. Hàm này kiểm tra xem char là ký tự điều khiển hay chữ cái có thể in bên trong phạm vi ascii. Đây là chuỗi suy nghĩ hiện tại của tôi:

  1. iscntrl ('a') được gọi và 'a' được chuyển đổi thành giá trị nguyên
  2. đầu tiên kiểm tra xem _c là -1 sau đó trả về 0 khác ...
  3. tăng địa chỉ con trỏ không xác định trỏ lên 1
  4. khai báo địa chỉ này như một con trỏ tới một mảng có độ dài (unsign char) ((int) 'a')
  5. áp dụng bitwise và toán tử cho _C (0x20) và mảng (???)

Bằng cách nào đó, thật kỳ lạ, nó hoạt động và mọi lúc khi 0 được trả về char _c đã cho không phải là một ký tự có thể in được. Mặt khác, khi nó có thể in, hàm chỉ trả về một giá trị số nguyên không được quan tâm đặc biệt. Vấn đề hiểu biết của tôi là ở bước 3, 4 (một chút) và 5.

Cảm ơn bạn đã giúp đỡ.


1
_ctype_về cơ bản là một mảng của bitmasks. Nó được lập chỉ mục bởi các nhân vật quan tâm. Vì vậy, _ctype_['A']sẽ chứa các bit tương ứng với "alpha" và "chữ hoa", _ctype_['a']sẽ chứa các bit tương ứng với "alpha" và "chữ thường", _ctype_['1']sẽ chứa một bit tương ứng với "chữ số", v.v ... Có vẻ như 0x20là bit tương ứng với "control" . Nhưng vì một số lý do, _ctype_mảng được bù 1, vì vậy các bit cho 'a'thực sự nằm trong _ctype_['a'+1]. (Đó có lẽ là để cho nó hoạt động EOFngay cả khi không có bài kiểm tra bổ sung.)
Steve Summit

Các diễn viên (unsigned char)là để chăm sóc khả năng các nhân vật được ký và tiêu cực.
Hội nghị thượng đỉnh Steve

Câu trả lời:


3

_ctype_dường như là một phiên bản nội bộ bị hạn chế của bảng biểu tượng và tôi đoán + 1rằng họ đã không bận tâm đến việc lưu chỉ mục 0của nó vì nó không thể in được. Hoặc có thể họ đang sử dụng bảng 1 chỉ mục thay vì 0 chỉ mục như tùy chỉnh trong C.

Tiêu chuẩn C ra lệnh này cho tất cả các hàm ctype.h:

Trong mọi trường hợp, đối số là một int, giá trị sẽ được biểu diễn dưới dạng unsigned charhoặc bằng giá trị của macroEOF

Đi qua mã từng bước:

  • int iscntrl(int _c)Các intloại thực sự là các ký tự, nhưng tất cả các hàm ctype.h được yêu cầu xử lý EOF, vì vậy chúng phải như vậy int.
  • Kiểm tra đối với -1là kiểm tra đối với EOF, vì nó có giá trị -1.
  • _ctype+1 là số học con trỏ để lấy địa chỉ của một mục mảng.
  • [(unsigned char)_c]chỉ đơn giản là một truy cập mảng của mảng đó, trong đó cast có ở đó để thực thi yêu cầu tiêu chuẩn của tham số có thể biểu diễn là unsigned char. Lưu ý rằng charthực sự có thể giữ một giá trị âm, vì vậy đây là lập trình phòng thủ. Kết quả của việc []truy cập mảng là một ký tự từ bảng ký hiệu bên trong của chúng.
  • Các &mặt nạ là có để có được một nhóm nào đó của nhân vật trong bảng ký hiệu. Rõ ràng tất cả các ký tự có bit 5 được đặt (mặt nạ 0x20) là các ký tự điều khiển. Không có ý nghĩa về điều này mà không xem bảng.
  • Bất cứ điều gì với tập 5 bit sẽ trả về giá trị được che bằng 0x20, là giá trị khác không. Điều này cho thấy yêu cầu của hàm trả về khác không trong trường hợp boolean true.

Điều đó không đúng khi các diễn viên đưa ra yêu cầu tiêu chuẩn rằng giá trị có thể được biểu diễn là unsigned char. Tiêu chuẩn yêu cầu giá trị đã * có thể biểu diễn dưới dạng unsigned charhoặc bằng EOF, khi thường trình được gọi. Các diễn viên chỉ đóng vai trò là lập trình phòng thủ của nhóm Cameron: Sửa lỗi của một lập trình viên vượt qua một chữ ký char(hoặc a signed char) khi onus ở trên họ để vượt qua một unsigned chargiá trị khi sử dụng ctype.hmacro. Cần lưu ý rằng điều này không thể sửa lỗi khi chargiá trị −1 được truyền trong một triển khai sử dụng for1 cho EOF.
Eric Postpischil

Điều này cũng cung cấp một lời giải thích của + 1. Nếu macro trước đây không chứa điều chỉnh phòng thủ này, thì nó có thể được thực hiện chỉ vì ((_ctype_+1)[_c] & _C)vậy, có một bảng được lập chỉ mục với các giá trị điều chỉnh trước to1 đến 255. Vì vậy, mục nhập đầu tiên không được bỏ qua và phục vụ mục đích. Khi ai đó sau đó thêm các nhân vật phòng thủ, EOFgiá trị của -1 sẽ không hoạt động với các nhân vật đó, vì vậy họ đã thêm toán tử có điều kiện để xử lý nó một cách đặc biệt.
Eric Postpischil

3

_ctype_là một con trỏ tới một mảng toàn cầu gồm 256 byte. Tôi không biết những gì _ctype_[0]được sử dụng cho. _ctype_[1]thông qua _ctype_[256]_đại diện cho các loại nhân vật của các ký tự 0, tương ứng, 255: _ctype_[c + 1]đại diện cho các loại nhân vật c. Đây là điều tương tự như việc _ctype_ + 1chỉ ra một mảng gồm 256 ký tự (_ctype_ + 1)[c]đại diện cho thể loại chiến lược của nhân vật c.

(_ctype_ + 1)[(unsigned char)_c]không phải là một tuyên bố. Đó là một biểu thức sử dụng toán tử mảng con. Đó là vị trí truy cập (unsigned char)_ccủa mảng bắt đầu từ (_ctype_ + 1).

Mã phôi _ctừ intđến unsigned charkhông thực sự cần thiết: các hàm ctype lấy các giá trị char được truyền tới unsigned char( charđược ký trên OpenBSD): một cuộc gọi chính xác là char c; … iscntrl((unsigned char)c). Chúng có ưu điểm là đảm bảo rằng không có lỗi tràn bộ đệm: nếu ứng dụng gọi iscntrlvới giá trị nằm ngoài phạm vi unsigned charvà không -1, thì hàm này trả về giá trị có thể không có ý nghĩa nhưng ít nhất sẽ không gây ra sự cố hoặc rò rỉ dữ liệu riêng tư xảy ra tại địa chỉ bên ngoài giới hạn mảng. Giá trị này thậm chí còn đúng nếu hàm được gọi char c; … iscntrl(c)miễn clà -1.

Lý do cho trường hợp đặc biệt với -1 là vì nó EOF. charVí dụ getchar, nhiều hàm C tiêu chuẩn hoạt động trên a , đại diện cho ký tự là một intgiá trị là giá trị char được bao bọc trong phạm vi dương và sử dụng giá trị đặc biệt EOF == -1để chỉ ra rằng không thể đọc được ký tự nào. Đối với các hàm như getchar, EOFchỉ ra phần cuối của tệp, do đó tên e nd- o f- f ile. Eric Postpischil gợi ý rằng mã ban đầu chỉ là return _ctype_[_c + 1]và điều đó có thể đúng: _ctype_[0]sẽ là giá trị cho EOF. Việc thực hiện đơn giản hơn này dẫn đến tràn bộ đệm nếu chức năng bị sử dụng sai, trong khi việc thực hiện hiện tại tránh điều này như đã thảo luận ở trên.

Nếu vlà giá trị được tìm thấy trong mảng, v & _Ckiểm tra nếu bit tại 0x20được đặt trong v. Các giá trị trong mảng là mặt nạ của các danh mục mà ký tự nằm trong: _Cđược đặt cho các ký tự điều khiển, _Uđược đặt cho các chữ cái viết hoa, v.v.


(_ctype_ + 1)[_c] sẽ sử dụng chỉ mục mảng chính xác theo quy định của tiêu chuẩn C, bởi vì trách nhiệm của người dùng là phải vượt qua một EOFhoặc một unsigned chargiá trị. Hành vi cho các giá trị khác không được xác định bởi tiêu chuẩn C. Các diễn viên không phục vụ để thực hiện hành vi theo yêu cầu của tiêu chuẩn C. Đó là một cách giải quyết được đưa vào để bảo vệ chống lại các lỗi do lập trình viên truyền không đúng giá trị ký tự âm. Tuy nhiên, nó không đầy đủ hoặc không chính xác (và không thể sửa được) vì giá trị ký tự −1 sẽ nhất thiết phải được coi là EOF.
Eric Postpischil

Điều này cũng cung cấp một lời giải thích của + 1. Nếu macro trước đây không chứa điều chỉnh phòng thủ này, thì nó có thể được thực hiện chỉ vì ((_ctype_+1)[_c] & _C)vậy, có một bảng được lập chỉ mục với các giá trị điều chỉnh trước to1 đến 255. Vì vậy, mục nhập đầu tiên không được bỏ qua và phục vụ mục đích. Khi ai đó sau đó thêm các nhân vật phòng thủ, EOFgiá trị của −1 sẽ không hoạt động với các nhân vật đó, vì vậy họ đã thêm toán tử có điều kiện để xử lý nó một cách đặc biệt.
Eric Postpischil

2

Tôi sẽ bắt đầu với bước 3:

tăng địa chỉ con trỏ không xác định trỏ lên 1

Con trỏ không được xác định. Nó chỉ được định nghĩa trong một số đơn vị biên dịch khác. Đó là những gì externphần nói với trình biên dịch. Vì vậy, khi tất cả các tệp được liên kết với nhau, trình liên kết sẽ giải quyết các tham chiếu đến nó.

Vì vậy, nó chỉ đến những gì?

Nó trỏ đến một mảng với thông tin về mỗi ký tự. Mỗi nhân vật có mục riêng của mình. Một mục là một đại diện bitmap của các đặc điểm cho nhân vật. Ví dụ: Nếu bit 5 được đặt, điều đó có nghĩa là ký tự đó là ký tự điều khiển. Một ví dụ khác: Nếu bit 0 được đặt, điều đó có nghĩa là ký tự là ký tự trên.

Vì vậy, một cái gì đó như (_ctype_ + 1)['x']sẽ có được các đặc điểm áp dụng cho 'x'. Sau đó, một bitwise và được thực hiện để kiểm tra xem bit 5 có được đặt hay không, tức là kiểm tra xem đó có phải là ký tự điều khiển hay không.

Lý do để thêm 1 có lẽ là chỉ số thực 0 được dành riêng cho một số mục đích đặc biệt.


1

Tất cả thông tin ở đây dựa trên việc phân tích mã nguồn (và kinh nghiệm lập trình).

Khai báo

extern const char *_ctype_;

báo cho trình biên dịch rằng có một con trỏ tới const charmột nơi nào đó được đặt tên _ctype_.

(4) Con trỏ này được truy cập dưới dạng một mảng.

(_ctype_ + 1)[(unsigned char)_c]

Diễn viên (unsigned char)_cđảm bảo giá trị chỉ số nằm trong phạm vi của unsigned char(0..255).

Số học con trỏ _ctype_ + 1có hiệu quả thay đổi vị trí mảng bằng 1 phần tử. Tôi không biết tại sao họ thực hiện mảng theo cách này. Sử dụng phạm vi _ctype_[1].. _ctype[256]cho các giá trị ký tự 0.. 255để lại giá trị _ctype_[0]không được sử dụng cho hàm này. (Phần bù của 1 có thể được thực hiện theo nhiều cách khác nhau.)

Truy cập mảng lấy một giá trị (loại char, để tiết kiệm không gian) bằng cách sử dụng giá trị ký tự làm chỉ mục mảng.

(5) Thao tác bitwise AND trích xuất một bit từ giá trị.

Rõ ràng giá trị từ mảng được sử dụng như một trường bit trong đó bit 5 (tính từ 0 bắt đầu ít nhất là bit đáng kể, = 0x20) là cờ cho "là ký tự điều khiển". Vì vậy, mảng chứa các giá trị trường bit mô tả các thuộc tính của các ký tự.


Tôi đoán họ đã di chuyển + 1con trỏ đến để làm rõ rằng họ đang truy cập các phần tử 1..256thay vì 1..255,0. _ctype_[1 + (unsigned char)_c]sẽ tương đương do chuyển đổi ngầm thành int. Và _ctype_[(_c & 0xff) + 1]thậm chí còn rõ ràng và súc tích hơn.
cmaster - phục hồi monica

0

Chìa khóa ở đây là để hiểu biểu thức (_ctype_ + 1)[(unsigned char)_c]làm gì (sau đó được đưa vào bitwise và hoạt động, & 0x20để có kết quả!

Câu trả lời ngắn: Nó trả về phần tử _c + 1của mảng được trỏ tới _ctype_.

Làm sao?

Đầu tiên, mặc dù bạn dường như nghĩ _ctype_không xác định nhưng thực tế không phải vậy! Tiêu đề khai báo nó như một biến bên ngoài - nhưng nó được định nghĩa trong (gần như chắc chắn) một trong những thư viện thời gian chạy mà chương trình của bạn được liên kết khi bạn xây dựng nó.

Để minh họa cách cú pháp tương ứng với lập chỉ mục mảng, hãy thử làm việc (thậm chí biên dịch) chương trình ngắn sau:

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

Vui lòng yêu cầu làm rõ thêm và / hoặc giải thích.


0

Các hàm được khai báo trong ctype.hcác đối tượng chấp nhận loại int. Đối với các ký tự được sử dụng làm đối số, người ta cho rằng chúng được đúc sơ bộ theo kiểu unsigned char. Ký tự này được sử dụng làm chỉ mục trong bảng xác định đặc tính của ký tự.

Có vẻ như kiểm tra _c == -1được sử dụng trong trường hợp khi _cchứa giá trị của EOF. Nếu không EOFthì _c được chuyển thành kiểu char không dấu được sử dụng làm chỉ mục trong bảng được chỉ ra bởi biểu thức _ctype_ + 1. Và nếu bit được chỉ định bởi mặt nạ 0x20được đặt thì ký tự là ký hiệu điều khiển.

Để hiểu biểu thức

(_ctype_ + 1)[(unsigned char)_c]

tính đến việc đăng ký mảng là một toán tử postfix được định nghĩa như

postfix-expression [ expression ]

Bạn có thể không viết như

_ctype_ + 1[(unsigned char)_c]

bởi vì biểu thức này tương đương với

_ctype_ + ( 1[(unsigned char)_c] )

Vì vậy, biểu thức _ctype_ + 1được đặt trong ngoặc đơn để có được một biểu thức chính.

Vì vậy, trên thực tế bạn có

pointer[integral_expression]

mang lại đối tượng của một mảng tại chỉ mục được tính là biểu thức integral_expressiontrong đó con trỏ là (_ctype_ + 1)(gere được sử dụng con trỏ arithmetuc) và integral_expressionđó là chỉ mục là biểu thức (unsigned char)_c.

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.