특히 malloc의 결과를 캐스팅하는 것이 위험한 것은 무엇입니까?


86

이제 사람들이 이것을 복제로 표시하기 전에 다음을 모두 읽었으며 그중 어느 것도 내가 찾고있는 답을 제공하지 않습니다.

  1. C FAQ : malloc의 반환 값을 캐스팅하는 데 어떤 문제가 있습니까?
  2. SO : malloc ()의 반환 값을 명시 적으로 캐스팅해야합니까?
  3. SO : C의 불필요한 포인터 캐스트
  4. SO : malloc의 결과를 캐스팅합니까?

C FAQ와 위의 질문에 대한 많은 답변은 캐스팅 malloc의 반환 값이 숨길 수 있다는 신비한 오류를 인용합니다 . 그러나 그들 중 어느 것도 실제로 그러한 오류의 구체적인 예를 제공하지 않습니다. 이제 경고 아니라 오류 라고 말한 것에주의하십시오 .

이제 다음 코드가 제공됩니다.

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

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

캐스트를 사용하거나 사용하지 않고 gcc 4.2로 위 코드를 컴파일하면 동일한 경고가 표시되고 프로그램이 올바르게 실행되고 두 경우 모두 동일한 결과를 제공합니다.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

그렇다면 누구든지 캐스트 malloc의 반환 값으로 인해 발생할 수있는 컴파일 또는 런타임 오류의 특정 코드 예제를 제공 할 수 있습니까? 아니면 이것은 도시의 전설일까요?

편집 나는이 문제에 대해 잘 쓰여진 두 가지 주장을 발견했습니다.

  1. 캐스팅에 찬성 : CERT 권고 : 메모리 할당 함수 호출의 결과를 할당 된 유형에 대한 포인터로 즉시 캐스팅합니다.
  2. Casting에 대해 (2012-02-14 기준 404 오류 : 2010-01-27 의 Internet Archive Wayback Machine 사본을 사용합니다 . {2016-03-18 : "robots.txt로 인해 페이지를 크롤링하거나 표시 할 수 없습니다."})

6
캐스팅 void포인터를 사용하면 코드를 C ++로 컴파일 할 수 있습니다. 어떤 사람들은 그 기능의 말을, 나는 그것이 버그 말하고 싶지만)
크리스토프

1
또한 캐스팅 대신 수행해야 할 작업을 설명하는 링크의 첫 번째 주석을 읽으십시오. securecoding.cert.org/confluence/display/seccode/…
Christoph

3
출연진을 포함시키기 위해 CERT의 조언을받을 것입니다. 또한 stdlib.h를 포함하는 것을 잊지 않을 것입니다. :)
Abhinav

1
다음은 캐스팅 malloc의 반환 값으로 인한 컴파일 런타임 오류의 SO- 예 입니다 int*. 64 비트 아치에서 캐스팅합니다 .
John_West 2015

1
이 질문에는 Cnot 태그가 지정되어 있습니다 C++(2 개의 다른 언어입니다). 따라서 (일부 답변에서와 같이) 어떤 토론도이 질문과 관련이 없습니다.
user3629249

답변:


66

컴파일러 오류가 발생 하지 않지만 컴파일러 경고가 발생 합니다. 인용 한 소스 (특히 첫 번째 소스 )에서 . 를 포함하지 않고 캐스트 사용할 때 예측할 수없는 런타임 오류가 발생할 수 있습니다 .stdlib.h

따라서 당신 측의 오류는 캐스트가 아니라 stdlib.h. 컴파일러는 가정 할 수 malloc함수가 반환 int따라서 변환 void*사실에 의해 반환 된 포인터 mallocint다음 포인터 타입에 의한 명시 적 캐스트에 있습니다. 일부 플랫폼에서는 int포인터가 다른 바이트 수를 차지할 수 있으므로 유형 변환으로 인해 데이터가 손상 될 수 있습니다.

다행히 최신 컴파일러는 실제 오류를 가리키는 경고를 제공합니다. gcc제공 한 출력을 참조하십시오. 암시 적 선언 ( int malloc(int))이 내장 malloc. 그래서 없이도 gcc알고있는 것 같습니다 .mallocstdlib.h

이 오류를 방지하기 위해 캐스트를 남기는 것은 대부분 글을 쓰는 것과 같은 이유입니다.

if (0 == my_var)

대신에

if (my_var == 0)

후자는 =및 을 혼동하면 심각한 버그로 이어질 수 ==있지만 첫 번째는 컴파일 오류로 이어질 수 있기 때문입니다. 저는 개인적으로 후자의 스타일이 제 의도를 더 잘 반영하고이 실수를하지 않기 때문에 선호합니다.

에서 반환 된 값을 캐스팅 할 때도 마찬가지입니다 malloc. 저는 프로그래밍에서 명시적인 것을 선호하며 일반적으로 사용하는 모든 함수에 대한 헤더 파일을 포함하도록 두 번 확인합니다.


2
컴파일러가 호환되지 않는 암시 적 선언에 대해 경고하기 때문에 컴파일러 경고에주의를 기울이는 한 이것은 문제가되지 않는 것 같습니다.
Robert S. Barnes

4
@Robert : 예, 컴파일러에 대한 특정 가정이 주어집니다. 사람들이 일반적으로 C를 작성하는 가장 좋은 방법에 대한 조언을 제공 할 때 조언을 받는 사람이 최신 버전의 gcc를 사용하고 있다고 가정 할 수 없습니다.
Steve Jessop

4
아, 두 번째 질문에 대한 대답은 호출자가 반환 값 (정수라고 생각하는 값)을 가져와 T *로 변환하는 코드를 포함하고 있다는 것입니다. 피 호출자는 반환 값 (void *)을 작성하고 반환합니다. 따라서 호출 규칙에 따라 : int 반환 및 void * 반환은 "동일한 위치"(등록 또는 스택 슬롯)에있을 수도 있고 없을 수도 있습니다. int와 void *는 크기가 같을 수도 있고 같지 않을 수도 있습니다. 두 사람 사이의 변환은 작동하지 않을 수도 있고 그렇지 않을 수도 있습니다. 따라서 "그냥 작동"하거나 값이 손상되거나 (아마도 일부 비트 손실) 호출자가 완전히 잘못된 값을 선택할 수 있습니다.
Steve Jessop

1
@ RobertS.Barnes는 파티에 늦었지만 : 반환 값은 일반적으로 C ++에서도 함수 서명의 일부가 아닙니다. 링커는 심볼로의 점프를 생성합니다.
Peter-Monica 복원

3
stdlib.h를 포함하지 않고 캐스트를 사용할 때 예측할 수없는 런타임 오류가 발생할 수 있습니다 . 사실이지만 포함하지 않는 stdlib.h것은 "암시 적 선언"경고 만 수신하더라도 이미 자체적으로 오류입니다.
Jabberwocky

45

결과를 캐스팅하는 것에 대한 좋은 상위 수준의 주장 중 하나 malloc는 종종 언급되지 않은 채로 남아 있지만, 제 생각에는 잘 알려진 하위 수준 문제보다 더 중요합니다 (선언이 누락되었을 때 포인터를 자르는 것과 같은).

좋은 프로그래밍 방법은 가능한 한 유형과 무관 한 코드를 작성하는 것입니다. 이는 특히 코드에서 유형 이름을 가능한 한 적게 언급하거나 전혀 언급하지 않는 것이 가장 좋다는 것을 의미합니다. 이는 캐스트 (불필요한 캐스트 방지), 인수로서의 유형 sizeof(에서 유형 이름 사용 방지 sizeof) 및 일반적으로 유형 이름에 대한 다른 모든 참조에 적용됩니다.

유형 이름은 선언에 속합니다. 가능한 한 형식 이름은 선언과 선언으로 만 제한되어야합니다.

이 관점에서이 코드는 나쁘다

int *p;
...
p = (int*) malloc(n * sizeof(int));

그리고 이것은 훨씬 낫다

int *p;
...
p = malloc(n * sizeof *p);

단순히 "결과를 캐스트하지 않기 때문"이 malloc아니라 유형 독립적 (또는 선호하는 경우 유형에 무관)이기 때문 p입니다. 사용자.


Fwiw, 나는 이것이 이것과 거의 같은 이유라고 생각합니다 : stackoverflow.com/questions/953112/… 그러나 DIY보다는 유형 독립성에 초점을 맞추 었습니다. 물론 첫 번째는 두 번째에서 이어 지거나 그 반대의 경우도 있으므로 적어도 가끔 언급 됩니다 . :)
언 와인드

5
@unwind 당신이 가장 가능성이 평균 DRY 보다는 DIY
kratenko

18

프로토 타입이 아닌 함수는를 반환하는 것으로 간주됩니다 int.

따라서를 int포인터로 캐스팅하고 있습니다. 포인터가 int플랫폼에서 s 보다 넓은 경우 이는 매우 위험한 동작입니다.

게다가, 물론, 어떤 사람들은 경고를 고려하는 것이 할 오류, 즉 코드는 그들없이 컴파일해야한다.

개인적 void *으로 다른 포인터 유형 으로 캐스트 할 필요가 없다는 사실이 C의 기능이라고 생각하며 깨질 코드를 고려합니다.


14
나는 컴파일러가 내가하는 것보다 언어에 대해 더 많이 알고 있다는 믿음을 가지고있다. 그래서 그것이 나에게 어떤 것에 대해 경고하면 나는주의를 기울인다.
György Andrasek

3
많은 프로젝트에서 C 코드는 void*.
laalto

nit : " 기본적 으로 프로토 타입이 아닌 함수는를 반환하는 것으로 간주됩니다 int." -프로토 타입이 아닌 함수의 반환 유형을 변경할 수 있다는 의미입니까?
pmg

1
@laalto-그렇습니다. 그러나 그렇게해서는 안됩니다. C는 C ++가 아니라 C이며 C ++ 컴파일러가 아닌 C 컴파일러로 컴파일해야합니다. 변명의 여지가 없습니다. GCC (최고의 C 컴파일러 중 하나)는 상상할 수있는 거의 모든 플랫폼에서 실행되며 고도로 최적화 된 코드도 생성합니다. 게으름과 느슨한 표준 외에 C ++ 컴파일러로 C를 컴파일해야하는 이유는 무엇입니까?
Chris Lutz

3
C와 C ++로 컴파일 할 수있는 코드의 예 : #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. 자, 왜 당신이 정적 인라인 함수에서 malloc을 호출하고 싶은지 정말 모르겠지만 두 가지 모두에서 작동하는 헤더는 거의 들어 본 적이 없습니다.
Steve Jessop 09-10-15

11

64 비트 모드에서 컴파일 할 때 이렇게하면 반환 된 포인터가 32 비트로 잘립니다.

편집 : 너무 짧아서 죄송합니다. 다음은 토론을위한 예제 코드입니다.

본관()
{
   char * c = (char *) malloc (2);
   printf ( "% p", c);
}

반환 된 힙 포인터가 0xAB00000000과 같이 int에서 표현할 수있는 것보다 크다고 가정합니다.

malloc이 포인터를 반환하도록 프로토 타입이 아닌 경우 반환 된 int 값은 초기에 모든 중요한 비트가 설정된 일부 레지스터에 있습니다. 이제 컴파일러는 "좋아, 어떻게 포인터로 int를 변환합니까"라고 말합니다. 이는 프로토 타입을 생략하여 malloc이 "반환"한다고 말한 하위 32 비트의 부호 확장 또는 0 확장이 될 것입니다. int가 서명되었으므로 변환은 부호 확장이 될 것이라고 생각합니다.이 경우 값을 0으로 변환합니다. 0xABF0000000의 반환 값을 사용하면 역 참조를 시도 할 때 재미를 유발하는 0이 아닌 포인터를 얻게됩니다.


1
이것이 어떻게 발생하는지 자세히 설명해 주시겠습니까?
Robert S. Barnes

5
나는 Peeter Joot가 "기본적으로 프로토 타입이 아닌 함수는 stdlib.h를 포함하여 int를 반환한다고 가정하고, sizeof (int)는 32 비트이고 sizeof (ptr)는 64 비트입니다."라는 것을 알아 냈습니다.
Test

4

재사용 가능한 소프트웨어 규칙 :

malloc ()을 사용하는 인라인 함수를 작성하는 경우 C ++ 코드에서도 재사용 할 수 있도록 명시 적 유형 캐스팅 (예 : (char *))을 수행하십시오. 그렇지 않으면 컴파일러가 불평합니다.


바라건대, (최근) gcc에 링크 시간 최적화가 포함됨에 따라 ( gcc.gnu.org/ml/gcc/2009-10/msg00060.html 참조 ), 헤더 파일에서 인라인 함수를 선언 할 필요가 없습니다
Christoph

당신은 나쁜 생각을 가지고 있습니다. 다른 컴파일러 / 버전 / 아키텍처간에 이식 가능하고 교차 플랫폼이 무엇인지 알고 있습니까? 좋아, 당신은 할 수 없습니다. 재사용 가능이란 무엇을 의미합니까?
테스트

2
C ++를 작성할 때 malloc / free는 올바른 방법이 아닙니다. 대신 new / delete를 사용하십시오. IE는 C ++ 코드에서 malloc / free에 대한 호출이 없음 / nada / zero가되어야합니다
user3629249

3
@ user3629249 : 내에서 사용할 수있을 필요가 함수 쓸 때 중 하나를 사용하여 C 코드 또는 C ++ 코드 malloc/ free모두 사용하는 것보다 더 나은 쉽다 위해 mallocC와 newC에서 + +를, 데이터 구조는 C와 C 사이에 공유 특히 ++ 객체가 C 코드로 생성되고 C ++ 코드로 릴리스되거나 그 반대의 경우가 발생할 가능성이 있습니다.
supercat

3

C의 void 포인터는 명시 적 캐스트없이 모든 포인터에 할당 될 수 있습니다. 컴파일러는 경고를 제공하지만 해당 유형 으로 유형 캐스팅 하여 C ++에서 재사용 할 수 있습니다 malloc(). C는 엄격한 유형 검사가 아니기 때문에 out 유형 캐스팅을 사용하면 C 에서도 사용할 수 있습니다 . 그러나 C ++는 엄격하게 유형 검사 이므로 C ++에서 유형 캐스트가 필요합니다 .malloc()


C ++에서 malloc을 사용한다면 타당한 이유가있을 것입니다! ; p
antred 2011
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.