Development Tip

부호 별 또는 크기별 유형과 비교하여 "int"를 사용해야하는 경우는 언제입니까?

yourdevel 2021. 1. 8. 22:29
반응형

부호 별 또는 크기별 유형과 비교하여 "int"를 사용해야하는 경우는 언제입니까?


나는이 프로그래밍 언어에 대한 약간의 VM을 그것은 32 비트 및 64 비트 아키텍처뿐만 아니라 모두 C 및 C ++에서 컴파일을 지원하고 C로 구현했습니다.

가능한 한 많은 경고를 활성화하여 깔끔하게 컴파일되도록 노력하고 있습니다. 을 켜면 CLANG_WARN_IMPLICIT_SIGN_CONVERSION새로운 경고가 연속적으로 표시됩니다.

int명시 적으로 서명되지 않은 유형 및 / 또는 명시 적으로 크기 가 지정된 유형과 비교하여 언제 사용할 지에 대한 좋은 전략을 갖고 싶습니다 . 지금까지 그 전략이 무엇인지 결정하는 데 어려움을 겪고 있습니다.

대부분 int지역 변수 및 매개 변수와 같은 항목에 사용하고 구조체의 필드에 더 좁은 유형을 사용하여 이들을 혼합 하면 많은 암시 적 변환 문제가 발생한다는 것은 사실입니다.

힙의 개체에 대한 메모리 사용을 명시 적으로 제어하는 ​​아이디어가 마음에 들기 때문에 구조체 필드에 대해보다 구체적으로 크기가 지정된 유형을 사용하는 것을 좋아합니다. 또한 해시 테이블의 경우 해시 할 때 서명되지 않은 오버플로에 의존하므로 해시 테이블의 크기가 uint32_t.

하지만 어디에서나 더 구체적인 유형을 사용하려고하면 모든 곳 에서 구불 구불 한 캐스트의 미로에 빠져 있습니다.

다른 C 프로젝트는 무엇을합니까?


int캐스팅의 필요성을 최소화하기 때문에 어디에서나 사용하는 것만으로도 유혹적인 것처럼 보일 수 있지만주의해야 할 몇 가지 잠재적 인 함정이 있습니다.

  • An int은 예상보다 짧을 수 있습니다. 대부분의 데스크탑 플랫폼에서 an int은 일반적으로 32 비트이지만 C 표준은 최소 16 비트 길이 만 보장합니다 . 코드 에 임시 값인 경우에도 2 16 -1 = 32,767 보다 큰 숫자가 필요할 수 있습니까? 그렇다면 int. ( long대신 a를 사용할 수 있습니다 . a long는 32 비트 이상이어야합니다.)

  • a조차도 long항상 충분히 길지는 않습니다. 특히 배열 (또는 배열 인 문자열)의 길이 charlong. 이를 위해 size_t(또는 ptrdiff_t부호 차이가 필요한 경우)를 사용하십시오 .

    특히 a size_t는 유효한 배열 인덱스를 보유 할 수있을만큼 충분히 크도록 정의되는 반면 an int또는 a long는 그렇지 않을 수 있습니다. 따라서 예를 들어 배열을 반복 할 때 루프 카운터 (및 초기 / 최종 값)는 일반적으로 size_t배열이 더 작은 유형이 작동 할 수있을만큼 충분히 짧은 지 확인하지 않는 한 일반적으로이어야합니다 . (: 반복하는 거꾸로 때주의해야 size_t하므로 서명한다 for(size_t i = n-1; i >= 0; i--)! 무한 루프 사용 i != SIZE_MAX또는 i != (size_t) -1불구하고 작동해야한다 또는 사용 do/의 while루프를하지만, 경우에 조심 n == 0!)

  • int서명되었습니다. 특히 이것은 int오버플로가 정의되지 않은 동작 임을 의미합니다 . 가치가 합법적으로 넘칠 수있는 위험이 있다면 int; 를 사용 unsigned int(또는 unsigned long, 또는 ) 대신.uintNN_t

  • 때로는 고정 된 비트 길이 만 필요합니다. ABI와 인터페이스하거나 특정 길이의 정수가 필요한 파일 형식을 읽고 / 쓰기하는 경우 사용해야하는 길이입니다. (물론, 그런 상황이라면 엔디안과 같은 것에 대해 걱정해야 할 수도 있고, 어쨌든 데이터를 바이트 단위로 수동 패킹해야 할 수도 있습니다.)

즉, 고정 길이 유형 int32_t을 항상 사용하지 않아야하는 이유도 있습니다. 항상 유형을 입력하는 것이 어색 할뿐만 아니라 컴파일러가 항상 32 비트 정수를 사용하도록하는 것이 항상 최적은 아닙니다. 기본 int크기는 예를 들어 64 비트 일 수 있습니다. 예를 들어 C99를 사용할 int_fast32_t 있지만 입력하기가 더 어색합니다.


따라서 최대한의 안전과 휴대 성을위한 개인적인 제안은 다음과 같습니다.

  • 다음과 같이 일반적인 헤더 파일에서 일상적인 사용위해 고유 한 정수 유형을 정의합니다 .

    #include <limits.h>
    typedef int i16;
    typedef unsigned int u16;
    #if UINT_MAX >= 4294967295U
      typedef int i32;
      typedef unsigned int u32;
    #else
      typedef long i32;
      typedef unsigned long i32;
    #endif
    

    이 유형은 크기가 충분히 크면 유형의 정확한 크기가 중요하지 않은 모든 곳에 사용합니다. 내가 제안한 유형 이름은 짧고 자체 문서화되어 있으므로 필요한 경우 캐스트에서 사용하기 쉽고 너무 좁은 유형 사용으로 인한 오류 위험을 최소화해야합니다.

    편리하게도 위에서 정의한 u32u16유형은 최소. 이상으로 보장 unsigned int되므로 승격되어 정의되지 않은 오버플로 동작을 유발할 염려없이 안전하게 사용할 수 있습니다 .int

  • 사용하여 size_t모든 배열의 크기와 인덱싱하지만, 및 기타 정수 유형 사이의 캐스팅 할 때주의해야합니다. 선택적으로 너무 많은 밑줄을 입력하고 싶지 않은 경우 typedef더 편리한 별칭도 사용할 수 있습니다.

  • 특정 비트 수에서 오버플로를 가정하는 계산의 경우를 사용 하거나 위에 정의 된대로 / 사용 하고 . 사용하기로 선택한 경우 예기치 않은 승진으로부터 자신을 보호하십시오 . 이를 수행하는 한 가지 방법은 다음과 같은 매크로를 사용하는 것입니다.uintNN_tu16u32&uintNN_tint

    #define u(x) (0U + (x))
    

    다음과 같이 안전하게 작성할 수 있습니다.

    uint32_t a = foo(), b = bar();
    uint32_t c = u(a) * u(b);  /* this is always unsigned multiply */
    
  • 특정 정수 길이가 필요한 외부 ABI의 경우 특정 유형을 다시 정의합니다. 예 :

    typedef int32_t fooint32;  /* foo ABI needs 32-bit ints */
    

    다시 말하지만,이 유형 이름은 크기와 목적에 대해 자체 문서화됩니다.

    If the ABI might actually require, say, 16- or 64-bit ints instead, depending on the platform and/or compile-time options, you can change the type definition to match (and rename the type to just fooint) — but then you really do need to be careful whenever you cast anything to or from that type, because it might overflow unexpectedly.

  • If your code has its own structures or file formats that require specific bitlengths, consider defining custom types for those too, exactly as if it was an external ABI. Or you could just use uintNN_t instead, but you'll lose a little bit of self-documentation that way.

  • For all these types, don't forget to also define the corresponding _MIN and _MAX constants for easy bounds checking. This might sound like a lot of work, but it's really just a couple of lines in a single header file.

Finally, remember to be careful with integer math, especially overflows. For example, keep in mind that the difference of two n-bit signed integers may not fit in an n-bit int. (It will fit into an n-bit unsigned int, if you know it's non-negative; but remember that you need to cast the inputs to an unsigned type before taking their difference to avoid undefined behavior!) Similarly, to find the average of two integers (e.g. for a binary search), don't use avg = (lo + hi) / 2, but rather e.g. avg = lo + (hi + 0U - lo) / 2; the former will break if the sum overflows.


You seem to know what you are doing, judging from the linked source code, which I took a glance at.

You said it yourself - using "specific" types makes you have more casts. That's not an optimal route to take anyway. Use int as much as you can, for things that do not mandate a more specialized type.

The beauty of int is that it is abstracted over the types you speak of. It is optimal in all cases where you need not expose the construct to a system unaware of int. It is your own tool for abstracting the platform for your program(s). It may also yield you speed, size and alignment advantage, depending.

In all other cases, e.g. where you want to deliberately stay close to machine specifications, int can and sometimes should be abandoned. Typical cases include network protocols where the data goes on the wire, and interoperability facilities - bridges of sorts between C and other languages, kernel assembly routines accessing C structures. But don't forget that sometimes you would want to in fact use int even in these cases, as it follows platforms own "native" or preferred word size, and you might want to rely on that very property.

With platform types like uint32_t, a kernel might want to use these (although it may not have to) in its data structures if these are accessed from both C and assembler, as the latter doesn't typically know what int is supposed to be.

To sum up, use int as much as possible and resort to moving from more abstract types to "machine" types (bytes/octets, words, etc) in any situation which may require so.

As to size_t and other "usage-suggestive" types - as long as syntax follows semantics inherent to the type - say, using size_t for well, size values of all kinds - I would not contest. But I would not liberally apply it to anything just because it is guaranteed to be the largest type (regardless if it is actually true). That's an underwater stone you don't want to be stepping on later. Code has to be self-explanatory to the degree possible, I would say - having a size_t where none is naturally expected, would raise eyebrows, for a good reason. Use size_t for sizes. Use offset_t for offsets. Use [u]intN_t for octets, words, and such things. And so on.

This is about applying semantics inherent in a particular C type, to your source code, and about the implications on the running program.

Also, as others have illustrated, don't shy away from typedef, as it gives you the power to efficiently define your own types, an abstraction facility I personally value. A good program source code may not even expose a single int, nevertheless relying on int aliased behind a multitude of purpose-defined types. I am not going to cover typedef here, the other answers hopefully will.


Keep large numbers that are used to access members of arrays, or control buffers as size_t.

For an example of a project that makes use of size_t, refer to GNU's dd.c, line 155.


Here are a few things I do. Not sure they're for everyone but they work for me.

  1. Never use int or unsigned int directly. There always seems to be a more appropriately named type for the job.
  2. If a variable needs to be a specific width (e.g. for a hardware register or to match a protocol) use a width-specific type (e.g. uint32_t).
  3. For array iterators, where I want to access array elements 0 thru n, this should also be unsigned (no reason to access any index less than 0) and I use one of the fast types (e.g. uint_fast16_t), selecting the type based on the minimum size required to access all array elements. For example, if I have a for loop that will iterate through 24 elements max, I'll use uint_fast8_t and let the compiler (or stdint.h, depending how pedantic we want to get) decide which is the fastest type for that operation.
  4. Always use unsigned variables unless there is a specific reason for them to be signed.
  5. If your unsigned variables and signed variables need to play together, use explicit casts and be aware of the consequences. (Luckily this will be minimized if you avoid using signed variables except where absolutely necessary.)

If you disagree with any of those or have recommended alternatives please let me know in the comments! That's the life of a software developer... we keep learning or we become irrelevant.


Always.

Unless you have specific reasons for using a more specific type, including you're on a 16-bit platform and need integers greater than 32767, or you need to ensure proper byte order and signage for data exchange over a network or in a file (and unless you're resource constrained, consider transferring data in "plain text," meaning ASCII or UTF8 if you prefer).

My experience has shown that "just use 'int'" is a good maxim to live by and makes it possible to turn out working, easily maintained, correct code quickly every time. But your specific situation may differ, so take this advice with a bit of well-deserved scrutiny.

ReferenceURL : https://stackoverflow.com/questions/29197964/when-should-i-just-use-int-versus-more-sign-specific-or-size-specific-types

반응형