문제

메모: 현상금을 설정하는 대상을 더 적절하게 반영하기 위해 질문을 완전히 재작업했습니다.이로 인해 이미 제공된 답변이 불일치하는 경우 양해해 주시기 바랍니다.이 질문에 대한 이전 답변이 도움이 될 수 있으므로 새 질문을 만들고 싶지 않았습니다.


저는 C 표준 라이브러리를 구현하는 중인데 표준의 특정 부분에 대해 혼란스러워합니다.

표준은 다음에서 허용되는 숫자 형식을 정의합니다. scanf 정의 측면에서 함수 계열(%d, %i, %u, %o, %x) strtol, strtoul, 그리고 strtod.

표준에는 다음과 같이 나와 있습니다. fscanf() 입력 스트림에는 최대 한 문자만 다시 넣기 때문에 일부 시퀀스는 strtol, strtoul 그리고 strtod 용납할 수 없다 fscanf (ISO/IEC 9899:1999, 각주 251)

그러한 차이를 나타낼 수 있는 몇 가지 값을 찾으려고 노력했습니다.16진수 접두사 "0x" 뒤에 16진수가 아닌 문자가 오는 것은 두 함수 계열이 다른 경우 중 하나임이 밝혀졌습니다.

흥미롭게도 사용 가능한 두 C 라이브러리 중 어느 것도 출력에 동의하지 않는 것 같습니다.(이 질문 끝에 있는 테스트 프로그램 및 예제 출력을 참조하세요.)

내가 듣고 싶은 것은 "0xz"를 구문 분석할 때 표준 준수 동작으로 간주되는 것은 무엇입니까?.이상적으로는 표준의 관련 부분을 인용하여 요점을 설명합니다.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    int i, count, rc;
    unsigned u;
    char * endptr = NULL;
    char culprit[] = "0xz";

    /* File I/O to assert fscanf == sscanf */
    FILE * fh = fopen( "testfile", "w+" );
    fprintf( fh, "%s", culprit );
    rewind( fh );

    /* fscanf base 16 */
    u = -1; count = -1;
    rc = fscanf( fh, "%x%n", &u, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, u, count );
    rewind( fh );

    /* strtoul base 16 */
    u = strtoul( culprit, &endptr, 16 );
    printf( "strtoul:             result %2d, consumed %d\n", u, endptr - culprit );

    puts( "" );

    /* fscanf base 0 */
    i = -1; count = -1;
    rc = fscanf( fh, "%i%n", &i, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, i, count );
    rewind( fh );

    /* strtol base 0 */
    i = strtol( culprit, &endptr, 0 );
    printf( "strtoul:             result %2d, consumed %d\n", i, endptr - culprit );

    fclose( fh );
    return 0;
}

/* newlib 1.14

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0
*/

/* glibc-2.8

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1
*/

/* Microsoft MSVC

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 0

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 0
*/

/* IBM AIX

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 1

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 1
*/
도움이 되었습니까?

해결책

Fred J.와의 커뮤니케이션comp.std.c에서 PL22.11(ANSI "C")의 부사장인 Tydeman이 이에 대해 설명했습니다.

fscanf

입력 항목은 일치하는 입력 순서의 가장 긴 입력 문자 [...]로 정의됩니다.(7.19.6.2 P9)

이로 인해 "0x"는 일치하는 입력 시퀀스의 접두사인 가장 긴 시퀀스가 ​​됩니다.(심지어 %i 16진수 "0x"는 10진수 "0"보다 긴 시퀀스이므로 변환됩니다.)

입력 항목이 읽은 후 첫 번째 문자 인 경우.(7.19.6.2 P9)

이것은 만든다 fscanf "z"를 읽고 일치하지 않는 것으로 다시 설정합니다(각주 251의 한 문자 푸시백 제한 준수).

입력 항목이 일치하는 순서가 아닌 경우 지침 실행에 실패합니다.이 조건은 일치하는 실패입니다.(7.19.6.2 P10)

이로 인해 "0x"가 일치하지 않게 됩니다. 즉, fscanf 값을 할당하지 않으면 0을 반환합니다(해당 %x 또는 %i 첫 번째 전환이었습니다.지정자), 입력 스트림에서 읽지 않은 첫 번째 문자로 "z"를 그대로 둡니다.

strtol

정의 strtol (그리고 strtoul) 한 가지 중요한 점이 다릅니다.

제목 시퀀스는 최초의 백인이 아닌 공간 문자로 시작하여 입력 문자열의 가장 긴 초기 하단으로 정의됩니다. 그것은 예상 형식입니다.(7.20.1.4 P4, 강조 광산)

의미하는 것은 strtol 가장 오랫동안 찾아야 해 유효한 시퀀스(이 경우에는 "0")입니다.가리켜야 한다 endptr "x"로 이동하고 결과로 0을 반환합니다.

다른 팁

구문 분석이 다른 결과를 낳을 수 있다고 생각하지 않습니다. Plaugher 참조는 그냥 지적하고 있습니다 strtol() 구현은 전체 문자열에 완전히 액세스 할 수 있으므로 더 효율적이고 효율적인 버전 일 수 있습니다.

C99 사양에 따르면 scanf() 기능의 가족은 strto*() 기능의 가족. 예를 들어, 변환 지정자의 경우 x 이것은 읽는다 :

선택적으로 서명 된 16 진수 정수와 일치하며, 그의 형식은 strtoul 값 16의 기능 base 논쟁.

그래서 만약 sscanf() 그리고 strtoul() 다른 결과를 제공하면 LIBC 구현은 일치하지 않습니다.

당신의 예상 결과 샘플 코드 그러나 조금 불분명해야합니다.

strtoul() 선택적 접두사를 허용합니다 0x 또는 0X 만약에 base ~이다 16, 그리고 사양이 읽습니다

제목 시퀀스는 입력 문자열의 가장 긴 초기 하단으로 정의되며, 이는 최초의 비 백색 공간 문자, 즉 예상 형식으로 시작됩니다.

문자열을 위해 "0xz", 내 생각에 예상 형식의 가장 긴 초기 후속은 다음과 같습니다. "0", 따라서 가치는 있어야합니다 0 그리고 endptr 인수를 설정해야합니다 x.

mingw-gcc 4.4.0은 동의하지 않고 둘 다로 문자열을 구문 분석하지 못합니다. strtoul() 그리고 sscanf(). 추론은 예상 양식의 가장 긴 초기 후속이 "0x" - 유효한 정수 문자가 아니므로 구문 분석이 수행되지 않습니다.

표준에 대한 이러한 해석이 잘못되었다고 생각합니다. 예상 형태의 후속 시퀀스는 항상 유효한 정수 값을 산출해야합니다 (범위를 벗어난 경우 MIN/MAX 값이 반환되고 errno 설정되었습니다 ERANGE).

Cygwin-GCC 3.4.4 (내가 아는 한 Newlib를 사용하는)도 문자를 구문 분석하지 않습니다. strtoul() 사용되지만 표준에 대한 내 해석에 따라 문자열을 구문 분석합니다. sscanf().

표준에 대한 나의 해석이 당신의 성공 문제에 취약하다는 것을 조심하십시오. 즉, 표준은 ungetc() 한 번. 있는지 결정합니다 0x 리터럴의 일부입니다. 앞으로 두 캐릭터를 읽어야합니다. x 그리고 다음 캐릭터. 16 진 문자가 없다면, 그들은 뒤로 밀려 야합니다. 구문 분석 할 토큰이 더 많으면 버퍼링 하고이 문제를 해결할 수 있지만 마지막 토큰이라면 ungetc() 두 캐릭터 모두.

나는 정말로 무엇을 확실하지 않습니다 fscanf() 해야한다면해야합니다 ungetc() 실패합니다. 스트림의 오류 표시기를 설정했을까요?

구문 분석 숫자시 표준에 따라 일어날 일을 요약하려면 :

  • 만약에 fscanf() 성공하면 결과는 strto*()
  • 대조적으로 strto*(), fscanf() IF가 실패합니다

    입력 문자의 가장 긴 순서 [...]는 일치하는 입력 순서의 접두사이거나 접두사입니다.

    정의에 따르면 fscanf() 아니다

    가장 긴 초기 후속 [...]은 예상 형식입니다.

    정의에 따르면 strto*()

이것은 다소 추악하지만 요구 사항의 필요한 결과입니다. fscanf() 탐욕 스럽지만 둘 이상의 캐릭터를 뒤로 밀 수는 없습니다.

일부 라이브러리 구현자는 다른 행동을 선택했습니다. 제 생각에는

  • 하자 strto*() 결과를 일관되게 만들지 못하는 것은 어리 석다 (나쁜 mingw)
  • 하나 이상의 캐릭터를 뒤로 밀고 있습니다 fscanf() 수락 된 모든 값을 수락합니다 strto*() 표준을 위반하지만 정당화됩니다 (그들이 보트를하지 않았다면 Newlib에 대한 Hurray strto*() :()
  • 일치하지 않는 캐릭터를 뒤로 밀지 않고 여전히 '예상 형태'의 캐릭터를 구문 분석하는 것만으로는 캐릭터가 얇은 공기로 사라질 때 모호한 것 같습니다 (나쁜 glibc)

나는 질문을 이해하지 못하지만, 한 가지에 대해서는 scanf ()가 EOF를 처리해야합니다. scanf ()와 strtol ()은 다른 종류의 짐승입니다. 아마도 strtol () 및 sscanf () 대신 비교해야합니까?

scanf ()를 구현하는 방법이 Ungetc ()와 어떻게 관련 될 수 있는지 잘 모르겠습니다. scanf ()는 스트림 버퍼의 모든 바이트를 사용할 수 있습니다. Ungetc ()는 단순히 바이트를 버퍼 끝으로 밀고 오프셋도 변경됩니다.

scanf("%d", &x);
ungetc('9', stdin);
scanf("%d", &y);
printf("%d, %d\n", x, y);

입력이 "100"인 경우 출력은 "100, 9"입니다. Scanf ()와 Ungetc ()가 서로를 방해 할 수있는 방법을 알지 못합니다. 순진한 댓글을 추가하면 죄송합니다.

입력에 대한 스캔프() 기능뿐만 아니라 strtol() 함수, 에서 비서.7.20.1.4 P7 나타내다: 주제 시퀀스가 ​​비어 있거나 예상한 형식이 아닌 경우 변환이 수행되지 않습니다.nptr의 값은 endptr이 널 포인터가 아닌 경우 endptr이 가리키는 객체에 저장됩니다..또한 다음 규칙에 따라 정의된 토큰을 구문 분석하는 규칙을 고려해야 합니다. 비서.6.4.4 상수, 에서 가리키는 규칙 비서.7.20.1.4 P5.

다음과 같은 나머지 행동은 오류 값은 구현에 따라 달라야 합니다.예를 들어 내 FreeBSD 상자에서 나는 EINVAL 그리고 에레인지 값을 참조하고 Linux에서도 동일한 일이 발생합니다. 에레인지 값이 잘못되었습니다.

질문을 다시 작성한 후 쓸모없는 답변. 그래도 주석의 흥미로운 링크.


의심스러운 경우 테스트를 작성하십시오. -- 속담

내가 생각할 수있는 변환 지정자와 입력 변형의 모든 조합을 테스트 한 후, 두 기능 패밀리가 맞다는 것이 맞습니다. 동일한 결과를 제공하지 마십시오. (적어도 GLIBC에서는 테스트 할 수있는 것입니다.)

차이점은 세 가지 상황이 충족 될 때 나타납니다.

  1. 너는 사용한다 "%i" 또는 "%x" (16 진수 입력 허용).
  2. 입력에는 (선택 사항)이 포함됩니다. "0x" 16 진주 접두사.
  3. 16 진전 접두사에 따라 유효한 16 진수 숫자는 없습니다.

예제 코드 :

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char * string = "0xz";
    unsigned u;
    int count;
    char c;
    char * endptr;

    sscanf( string, "%x%n%c", &i, &count, &c );
    printf( "Value: %d - Consumed: %d - Next char: %c - (sscanf())\n", u, count, c );
    i = strtoul( string, &endptr, 16 );
    printf( "Value: %d - Consumed: %td - Next char: %c - (strtoul())\n", u, ( endptr - string ), *endptr );
    return 0;
}

산출:

Value: 0 - Consumed: 1 - Next char: x - (sscanf())
Value: 0 - Consumed: 0 - Next char: 0 - (strtoul())

이것은 나를 혼란스럽게합니다. 확실히 sscanf() The에서 구제하지 않습니다 'x', 또는 구문 분석 할 수 없습니다 어느 "0x" 접두어 육각형. 그래서 그것은 읽었습니다 'z' 그리고 일치하지 않는 것을 발견했습니다. 그러나 그것은 선두 만 사용하기로 결정합니다 "0" 가치로. 그것은 밀어 붙이는 것을 의미합니다 'z' 그리고 그만큼 'x' 뒤. (예, 알고 있습니다 sscanf(), 내가 여기서 쉽게 테스트하기 위해 사용했던 것은 스트림에서 작동하지 않지만 그들이 모두를 만들었다고 강력하게 생각합니다. ...scanf() 기능은 일관성을 위해 동일하게 행동합니다.)

그래서 ... 1 차 ungetc() 실제로 이유가 아닙니다 ...? :-/

예, 결과가 다릅니다. 그래도 여전히 제대로 설명 할 수는 없습니다 ... :-(

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top