비트 단위 및 연산자를 사용하여 배열 선언에 대한 C 포인터


9

다음 코드를 이해하고 싶습니다.

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

obenbsd 운영 체제 소스 코드의 ctype.h 파일에서 시작됩니다 . 이 함수는 문자가 제어 문자인지 또는 ASCII 범위 내의 인쇄 가능한 문자인지 확인합니다. 이것은 내 현재 생각의 사슬입니다.

  1. iscntrl ( 'a')이 호출되고 'a'가 정수 값으로 변환됩니다
  2. 먼저 _c가 -1인지 확인한 다음 0을 반환하십시오.
  3. 정의되지 않은 포인터가 가리키는 주소를 1 씩 증가시킵니다.
  4. 이 주소를 길이의 배열에 대한 포인터로 선언하십시오 (부호없는 char) ((int) 'a')
  5. 비트 및 연산자를 _C (0x20) 및 배열 (???)에 적용

어쨌든, 이상하게도 작동하며 0이 반환 될 때마다 주어진 char _c는 인쇄 가능한 문자가 아닙니다. 그렇지 않으면 인쇄 가능하면 함수는 특별한 관심이없는 정수 값을 반환합니다. 이해의 문제는 3, 4 단계 (5)와 5 단계입니다.

도움을 주셔서 감사합니다.


1
_ctype_본질적으로 비트 마스크의 배열입니다. 관심있는 캐릭터가 색인을 생성하고 있습니다. 따라서 _ctype_['A']"알파"와 "대문자"에 _ctype_['a']해당하는 비트를 포함하고 "알파"와 "소문자"에 _ctype_['1']해당하는 비트를 포함하고 "숫자"에 해당하는 비트를 포함합니다. 0x20"제어"에 해당하는 비트는 . 그러나 어떤 이유로 _ctype_배열은 1만큼 오프셋되므로 비트 'a'는 실제로 in _ctype_['a'+1]입니다. (아마이었다가 작동하도록하기 위해 EOF추가 테스트없이도.)
스티브 정상 회의

캐스트 (unsigned char)는 캐릭터가 서명되고 부정적인 가능성을 처리하는 것입니다.
Steve Summit

답변:


3

_ctype_기호 테이블의 제한된 내부 버전으로 보이며 인쇄 할 수 없기 때문에 + 1인덱스 색인 0을 저장하지 않아도된다고 생각합니다 . 또는 C의 사용자 지정과 같이 0 인덱스 대신 1 인덱스 테이블을 사용하고 있습니다.

C 표준은 모든 ctype.h 함수에 대해이를 지시합니다.

모든 경우에 논쟁은 int , 그 값은 unsigned char매크로의 값 으로 나타내 거나 매크로의 값과 같아야합니다.EOF

코드를 단계별로 살펴보십시오.

  • int iscntrl(int _c)int종류는 정말 문자입니다 만, 모든 ctype.h 함수는 핸들에 필요한EOF 그들이해야하므로 int.
  • 에 대한 점검 -1은에 대한 점검EOF 합니다 -1.
  • _ctype+1 배열 항목의 주소를 얻기위한 포인터 산술입니다.
  • [(unsigned char)_c]는 단순히 해당 배열에 대한 배열 액세스입니다. 여기서 캐스트는로 표현할 수있는 매개 변수의 표준 요구 사항을 시행합니다 unsigned char. 참고 char이 방어 프로그래밍 그래서 실제로, 음의 값을 보유 할 수 있습니다. 의 결과[]어레이 액세스 내부 심볼 테이블의 단일 문자입니다.
  • 그만큼 &마스킹은 심볼 테이블에서 특정 문자 그룹을 얻을 수있다. 분명히 비트 5가 설정된 모든 문자 (마스크 0x20)는 제어 문자입니다. 테이블을 보지 않고는 의미가 없습니다.
  • 비트 5가 설정된 모든 값은 0이 아닌 0x20으로 마스킹 된 값을 반환합니다. 이것은 부울 true의 경우 함수가 0이 아닌 값을 리턴하도록 요구합니다.

캐스트가 값을로 표현할 수있는 표준 요구 사항을 충족시키는 것은 올바르지 않습니다 unsigned char. 이 표준은 값이 있어야 이미 * 로서 표현할 수 unsigned char, 또는 동일한 EOF루틴이 호출 될 때. 캐스트는 "방어적인"프로그래밍의 역할 만합니다 . 매크로를 사용할 때 onus가 값 을 전달하기 위해 서명 한 char(또는 a signed char)를 전달한 프로그래머의 오류 수정 . 에 대해 -1을 사용하는 구현에서 -1 값이 전달 되면 오류를 수정할 수 없습니다 . unsigned charctype.hcharEOF
Eric Postpischil

이것에 대한 설명도 제공합니다 + 1. 매크로에 이전에이 방어 조정이 포함되어 있지 않은 경우, 마치으로 구현 ((_ctype_+1)[_c] & _C)되어 사전 조정 값 -1 ~ 255로 색인이 생성 된 테이블을 가질 수있었습니다 . 따라서 첫 번째 항목을 건너 뛰지 않고 목적을 달성했습니다. 누군가 나중에 방어 형 캐스트를 추가 할 때 EOF−1 의 값은 해당 캐스트와 함께 작동하지 않으므로 조건부 연산자를 추가하여 특수하게 처리했습니다.
Eric Postpischil

3

_ctype_257 바이트의 전역 배열에 대한 포인터입니다. 나는 무엇이 _ctype_[0]사용 되는지 모른다 . _ctype_[1]...을 통하여_ctype_[256]_ 문자 0의 문자 종류를 나타내고, ..., 255는 각각 : _ctype_[c + 1]문자의 종류를 나타낸다 c. 이것은 _ctype_ + 1문자 (_ctype_ + 1)[c]의 범주를 나타내는 256 자 배열 을 가리키는 것과 같습니다 c.

(_ctype_ + 1)[(unsigned char)_c]선언이 아닙니다. 배열 첨자 연산자를 사용하는 표현식입니다. (unsigned char)_c에서 시작하는 배열의 위치 에 액세스 하고 (_ctype_ + 1)있습니다.

코드 캐스트 _c에서 int하려면 unsigned char꼭 필요한 것은 : ctype 함수가 캐스팅 문자 값을 unsigned char( char오픈 BSD에 서명) : 올바른 전화입니다 char c; … iscntrl((unsigned char)c). 버퍼 오버 플로우가 없음을 보장하는 이점이 있습니다. 응용 프로그램 iscntrl이 범위를 벗어난 값 unsigned char과 -1이 아닌 값으로 호출 하는 경우이 함수는 의미는 없지만 최소한 원인이되지 않는 값을 리턴합니다. 어레이 경계 외부의 주소에 발생한 개인 데이터의 충돌 또는 유출 함수가 호출되는 경우,이 값도 정확한 char c; … iscntrl(c)길이만큼 c되지 -1.

-1을 가진 특별한 경우의 이유는입니다 EOF. char예를 들어 getchar, 에서 작동하는 많은 표준 C 함수 는 문자를 int양의 범위로 래핑 된 문자 값인 값 EOF == -1으로 나타내고 특수 값 을 사용하여 문자를 읽을 수 없음을 나타냅니다. 같은 함수를 들면 getchar, EOF파일의 단부, 따라서 이름을 나타내는 E nd- O F- F ILE한다.Eric Postpischil 은 코드가 원래 단지였으며 return _ctype_[_c + 1]아마도 맞을 것이라고 제안합니다 ._ctype_[0] EOF의 가치가 될 것입니다. 이 간단한 구현은 함수가 잘못 사용되면 버퍼 오버 플로우를 발생시키는 반면, 현재 구현에서는 위에서 설명한대로이를 방지합니다.

경우 v배열에있는 값이며, v & _C비트의 경우이 테스트 0x20설정된다 v. 배열의 값은 문자가 포함 된 범주의 마스크입니다. _C제어 문자 _U로 설정되고 대문자로 설정됩니다.


(_ctype_ + 1)[_c] 것이다 C 표준에 의해 지정된대로 통과하거나 사용자의 책임이 있기 때문에, 정확한 배열 인덱스를 사용 EOF하거나 unsigned char값. 다른 값의 동작은 C 표준에 의해 정의되지 않습니다. 캐스트는 C 표준에 필요한 동작을 구현하는 데 사용되지 않습니다. 음수 문자 값을 잘못 전달하는 프로그래머로 인한 버그를 방지하기위한 해결 방법입니다. 그러나 -1 문자 값은 반드시로 취급되므로 불완전하거나 올바르지 않습니다 (수정할 수 없음) EOF.
Eric Postpischil

이것에 대한 설명도 제공합니다 + 1. 매크로에 이전에이 방어 조정이 포함되어 있지 않은 경우, 매크로 조정은 단순히 ((_ctype_+1)[_c] & _C)-1에서 255까지의 사전 조정 값으로 색인 된 테이블을 갖는 것처럼 구현되었을 수 있습니다 . 따라서 첫 번째 항목은 건너 뛰지 않고 목적을 달성했습니다. 누군가 나중에 방어 형 캐스트를 추가하면 EOF−1 의 값이 해당 캐스트와 함께 작동하지 않으므로 조건부 연산자를 추가하여 특수하게 처리합니다.
Eric Postpischil

2

3 단계부터 시작하겠습니다.

정의되지 않은 포인터가 가리키는 주소 를 1 씩 증가시킵니다.

포인터가 정의 되어 있지 않습니다. 방금 다른 컴파일 단위로 정의되었습니다. 그게 뭐야extern 부분이 컴파일러에게 알려주 입니다. 따라서 모든 파일이 서로 연결되면 링커에서 해당 파일에 대한 참조를 확인합니다.

그래서 무엇을 가리 킵니까?

각 문자에 대한 정보가있는 배열을 가리 킵니다. 각 캐릭터는 자신의 항목이 있습니다. 항목은 캐릭터 특성의 비트 맵 표현입니다. 예를 들어, 비트 5가 설정되면 문자가 제어 문자임을 의미합니다. 다른 예 : 비트 0이 설정되면 문자가 상위 문자임을 의미합니다.

따라서 비슷한 (_ctype_ + 1)['x']특성이 적용됩니다.'x' . 그런 다음 비트 단위로 비트 5가 설정되어 있는지 확인합니다. 즉, 제어 문자인지 여부를 확인합니다.

1을 추가 한 이유는 실제 인덱스 0이 특정 목적으로 예약되어 있기 때문일 수 있습니다.


1

여기에있는 모든 정보는 소스 코드 (및 프로그래밍 경험) 분석을 기반으로합니다.

선언

extern const char *_ctype_;

컴파일러에게 const charnamed이라는 곳에 대한 포인터가 있다고 알려줍니다 _ctype_.

(4)이 포인터는 배열로 액세스됩니다.

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

캐스트 (unsigned char)_c는 인덱스 값이 unsigned char(0..255) 범위에 있는지 확인합니다 .

포인터 산술 _ctype_ + 1은 배열 위치를 1 요소만큼 효과적으로 이동시킵니다. 왜 이런 식으로 배열을 구현했는지 모르겠습니다. 문자 값에 .. 을 사용하면 _ctype_[1].. _ctype[256]값이 남습니다.0255_ctype_[0] 이 함수 미사용. (1의 오프셋은 여러 가지 다른 방법으로 구현 될 수 있습니다.)

배열 액세스 char는 문자 값을 배열 색인으로 사용하여 공간을 절약하기 위해 유형의 값을 검색합니다 .

(5) 비트 AND 연산은 값에서 단일 비트를 추출합니다.

분명히 배열의 값은 비트 필드로 사용되며 비트 5 (최소한 비트부터 시작하여 0부터 계산 0x20)는 "제어 문자입니다"에 대한 플래그입니다. 따라서 배열에는 문자의 속성을 설명하는 비트 필드 값이 포함됩니다.


나는 그들이 + 1포인터 1..256대신 요소에 액세스하고 있음을 분명히하기 위해 포인터 로 옮겼습니다 1..255,0. _ctype_[1 + (unsigned char)_c]로의 암시 적 변환으로 인해 동등했을 것 int입니다. 그리고 _ctype_[(_c & 0xff) + 1]더 명확하고 간결했을 것입니다.
cmaster-monica reinstate

0

여기서 핵심은 표현이 무엇인지 이해하는 것입니다 (_ctype_ + 1)[(unsigned char)_c]다음에 공급되는 (수행 비트 및 운영,& 0x20 결과를 얻기 위해 !

짧은 대답 :가 _c + 1가리키는 배열의 요소 를 반환합니다 _ctype_.

어떻게?

당신이 생각하는 것하지만 우선 _ctype_되어 정의되지 않은 사실을하지 않습니다! 헤더 외부 변수로 선언 하지만 프로그램을 빌드 할 때 프로그램이 연결되는 런타임 라이브러리 중 하나에 정의되어 있습니다.

구문이 배열 인덱싱에 해당하는 방법을 설명하려면 다음 짧은 프로그램을 통해 컴파일 (컴파일)하십시오.

#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;
}

추가 설명 및 / 또는 설명을 요청하십시오.


0

에 선언 된 함수는 ctype.h유형의 객체를 받아들입니다 int. 인수로 사용되는 문자의 경우 문자가 유형으로 예비 캐스팅 된 것으로 가정합니다 unsigned char. 이 문자는 문자 특성을 결정하는 표에서 색인으로 사용됩니다.

에 값이 포함 _c == -1된 경우 검사 가 사용 된 것 같습니다 . 그렇지 않은 경우 _c는 표현식에서 가리키는 테이블의 인덱스로 사용되는 unsigned char 유형으로 캐스팅됩니다 . 그리고 마스크 로 지정된 비트 가 설정되면 문자는 제어 기호입니다._cEOFEOF_ctype_ + 10x20

표현을 이해하려면

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

배열 첨자가 다음과 같이 정의 된 접미사 연산자라는 점을 고려하십시오.

postfix-expression [ expression ]

당신은 같은 쓸 수 없습니다

_ctype_ + 1[(unsigned char)_c]

이 표현은

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

따라서 표현식 _ctype_ + 1은 기본 표현식을 얻기 위해 괄호로 묶습니다.

실제로 당신은

pointer[integral_expression]

그 산출 식으로 계산되는 인덱스의 배열의 오브젝트 integral_expression포인터가 (_ctype_ + 1)(포인터 기어의 arithmetuc를 사용) 및 integral_expression그 인덱스는 식이다 (unsigned char)_c.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.