C의 숨겨진 기능


141

모든 C 컴파일러 구현에는 표준이 있으므로 숨겨진 기능이 없어야합니다. 그럼에도 불구하고, 나는 모든 C 개발자들이 항상 사용하는 숨겨진 / 비법을 가지고 있다고 확신합니다.


이 질문의 C # 및 Perl 버전과 같이 가장 숨겨진 기능을 선택하도록“질문”을 편집하는 것이 좋을 것입니다.
Donal Fellows

답변:


62

함수 포인터. 함수 포인터 테이블을 사용하여 빠른 간접 스레드 코드 인터프리터 (FORTH) 또는 바이트 코드 디스패처를 구현하거나 OO와 유사한 가상 메소드를 시뮬레이션 할 수 있습니다.

그런 다음 qsort (), bsearch (), strpbrk (), strcspn ()과 같은 표준 라이브러리에 숨겨진 gem이 있습니다.

C의 잘못된 기능은 부호있는 산술 오버플로가 정의되지 않은 동작 (UB)이라는 것입니다. 따라서 부호있는 정수 인 x + y와 같은 표현식이 나타날 때마다 오버플로가 발생하여 UB가 발생할 수 있습니다.


29
그러나 오버플로에서 동작을 지정한 경우 일반적인 동작이 아닌 아키텍처에서는 속도가 느려질 수 있습니다. 매우 낮은 런타임 오버 헤드는 항상 C의 설계 목표였으며, 이는 이와 같은 많은 것들이 정의되어 있지 않음을 의미했습니다.
Mark Baker

9
overflow가 UB 인 이유를 잘 알고 있습니다. 표준에는 최소한 UB를 유발하지 않고 (모든 기본 연산의) 산술 오버플로를 테스트 할 수있는 라이브러리 루틴이 최소한 제공되어 있어야하기 때문에 여전히 잘못된 기능입니다.
zvrba

2
@zvrba, "모든 기본 연산의 산술 오버플로를 테스트 할 수있는 라이브러리 루틴"이 추가 된 경우 정수 산술 연산에 대해 상당한 성능 저하가 발생했을 수 있습니다. ===== 사례 연구 Matlab은 특히 랩핑 또는 포화에 대한 정수 오버플로 동작을 제어하는 ​​기능을 ADDS합니다. 또한 오버플로가 발생할 때마다 예외가 발생합니다. ==> Matlab 정수 연산의 성능 : 매우 느림. 내 자신의 결론 : Matlab은 정수 오버플로 검사를 원하지 않는 이유를 보여주는 매력적인 사례 연구라고 생각합니다.
Trevor Boyd Smith

15
나는 표준이 산술 오버플로를 검사하기위한 라이브러리 지원을 제공해야한다고 말했다 . 이제 라이브러리 루틴을 사용하지 않으면 어떻게 라이브러리 루틴에서 성능이 저하 될 수 있습니까?
zvrba 2016 년

5
큰 단점은 GCC에 부호있는 정수 오버플로를 포착하고 런타임 예외를 throw하는 플래그가 없다는 것입니다. 이러한 경우를 탐지하기위한 x86 플래그가 있지만 GCC는이를 사용하지 않습니다. 이러한 플래그를 사용하면 성능이 중요하지 않은 (특히 레거시) 응용 프로그램에서 코드 검토 및 리팩토링을 최소화하거나 최소화하지 않고도 보안의 이점을 얻을 수 있습니다.
Andrew Keeton 2016 년

116

GCC 컴파일러의 더 많은 트릭이지만 컴파일러에 분기 표시 힌트를 제공 할 수 있습니다 (Linux 커널에서 일반적 임)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

참조 : http://kerneltrap.org/node/4705

내가 이것에 대해 좋아하는 것은 또한 일부 기능에 표현력을 추가한다는 것입니다.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
이 속임수는 멋지다 ... :) 특히 당신이 정의한 매크로들과 함께. :)
sundar-복원 모니카

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

이들은 표준에서 선택적인 항목이지만 사람들이 끊임없이 재정의하고 있기 때문에 숨겨진 기능이어야합니다. 내가 작업 한 코드베이스 (현재는 여전히)에는 여러 식별자가있는 여러 재정의가 있습니다. 대부분의 경우 전 처리기 매크로를 사용합니다.

#define INT16 short
#define INT32  long

등등. 머리카락을 뽑고 싶어요. 괴물 표준 정수 타입 정의를 사용하십시오!


3
나는 그들이 C99 정도라고 생각합니다. 나는 이것이 주변에있을 수있는 휴대용 방법을 찾지 못했습니다.
akauppi

3
그것들은 C99의 선택적 부분이지만 이것을 구현하지 않는 컴파일러 공급 업체는 없습니다.
벤 콜린스

10
stdint.h는 C99에서 선택 사항이 아니지만 C99 표준을 따르는 것은 일부 공급 업체 ( 기침 Microsoft)를위한 것입니다.
벤 콤비

5
@Pete, 항문을 만들고 싶다면 : (1)이 스레드는 Microsoft 제품과 관련이 없습니다. (2)이 스레드는 C ++과 전혀 관련이 없습니다. (3) C ++ 97과 같은 것은 없습니다.
Ben Collins

5
azillionmonkeys.com/qed/pstdint.h를 살펴보십시오 -휴대하기
쉬운

73

쉼표 연산자는 널리 사용되지 않습니다. 확실히 남용 될 수 있지만 매우 유용 할 수도 있습니다. 이 사용이 가장 일반적입니다.

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

그러나이 연산자는 어디에서나 사용할 수 있습니다. 관찰 :

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

각 명령문은 평가되지만 표현식의 값은 마지막으로 평가 된 명령문의 값입니다.


7
CI의 20 년 만에 결코 본 적이 없습니다!
Martin Beckett

11
C ++에서는 오버로드 할 수도 있습니다.
Wouter Lievens

6
물론! = 할 수 있습니다. 과부하로 인한 위험은 빌트인이 빈 공간을 포함하여 이미 모든 것에 적용되므로 사용 가능한 과부하가 없어도 컴파일에 실패하지 않습니다. 즉, 프로그래머에게 많은 밧줄을 제공합니다.
Aaron

루프 내부의 int는 C와 함께 작동하지 않습니다 .C ++ 개선입니다. ","는 (i = 0, j = 10; i <j; j--, i ++)와 동일한 작업입니까?
Aif

63

구조를 0으로 초기화

struct mystruct a = {0};

이것은 모든 구조 요소를 0으로 만듭니다.


2
그러나 패딩은 0이 아닙니다.
Mikeage

2
@simonn, 아니요 비 구조적 유형을 포함하는 구조는 정의되지 않은 동작을 수행하지 않습니다. float / double의 메모리에서 0으로 memset은 float / double을 해석 할 때 여전히 0이됩니다 (float / double은 의도적으로 설계되었습니다).
Trevor Boyd Smith

6
@Andrew : memset/ calloc"모든 바이트를 0으로"(물리적으로 0)를 수행하십시오. 실제로 모든 유형에 대해 정의 된 것은 아닙니다. 적절한 논리적 제로 값으로 모든 것을{ 0 } 강화 합니다. 예를 들어 포인터는 주어진 플랫폼의 null 값이 인 경우에도 적절한 null 값을 얻도록 보장됩니다 . 0xBAADFOOD
AnT

1
@ nvl : 객체가 차지하는 모든 메모리를 강제로 모든 비트 0 상태로 설정하면 물리적 0이됩니다. 이것이하는 일입니다 memset( 0두 번째 주장으로). 당신은 얻을 논리적 당신이 / 할당 초기화 할 때 제로 0(또는 { 0 }소스 코드에서 객체). 이 두 종류의 0은 반드시 동일한 결과를 생성하지는 않습니다. 포인터가있는 예에서와 같이. 당신이 할 때 memset포인터에, 당신은 얻을 0x0000포인터를. 그러나 0포인터 를 할당 하면 물리적 포인터0xBAADF00D 또는 기타 이 될 수있는 null 포인터 값 이 표시됩니다.
AnT

3
@ nvl : 글쎄, 실제로 차이는 종종 개념적 일뿐입니다. 그러나 이론적으로는 거의 모든 유형이 가능합니다. 예를 들면 다음과 같습니다 double. 일반적으로 논리적 영점과 물리적 영점이 동일한 IEEE-754 표준에 따라 구현됩니다. 그러나이 언어에는 IEEE-754가 필요하지 않습니다. 따라서 double d = 0;(논리적 0) 할 때 실제로 메모리에서 차지하는 일부 비트가 0이 아닌 경우가 발생할 d수 있습니다.
AnT

52

다중 문자 상수 :

int x = 'ABCD';

이 세트 x0x41424344(또는 0x44434241, 아키텍처에 따라 다름).

편집 : 이 기술은 특히 int를 직렬화하는 경우 이식성이 없습니다. 그러나 자체 문서화 열거 형을 만드는 것이 매우 유용 할 수 있습니다. 예 :

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

따라서 원시 메모리 덤프를보고 열거 형 값을 조회하지 않고도 값을 결정해야하는 경우 훨씬 간단 해집니다.


나는 이것이 휴대용 구조가 아니라고 확신합니다. 다중 문자 상수를 생성 한 결과는 구현에 따라 정의됩니다.
Mark Bessey

8
"휴대용이 아닙니다"라는 의견은 그 요점을 완전히 놓치고 있습니다. INT_MAX는 "휴대용이 아닙니다"라는 이유로 INT_MAX를 사용하는 프로그램을 비판하는 것과 같습니다.이 기능은 필요한만큼 휴대 성이 뛰어납니다. 다중 문자 상수는 고유 한 정수 ID를 생성하기위한 읽기 가능한 방법을 제공하는 매우 유용한 기능입니다.
AnT

1
@Chris Lutz-후행 쉼표가 K & R로 돌아가는 것이 확실합니다. 제 2 판 (1988)에 설명되어 있습니다.
Ferruccio

1
@ Ferruchio : 당신은 집계 initailizer 목록에서 후행 쉼표에 대해 생각하고 있어야합니다. 열거 형 선언의 마지막 쉼표는 최근 추가 된 C99입니다.
AnT

3
당신은 'HANG'또는 'BSOD':-) 잊고
JBRWilkinson

44

나는 비트 필드를 사용한 적이 없지만 초저 레벨의 물건에는 멋지게 들립니다.

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

sizeof(cat), 이만큼 작을 수 있습니다 sizeof(char).


Aaronleppie의 의견에 감사의 말을 전합니다.


임베디드 시스템이나 저수준 드라이버 코드에서 구조체와 공용체의 조합이 훨씬 더 흥미 롭습니다. 예를 들어 SD 카드의 레지스터를 구문 분석하려는 경우 공용체 (1)를 사용하여 읽고 비트 필드의 구조 인 공용체 (2)를 사용하여 읽을 수 있습니다.
ComSubVie

5
비트 필드는 이식성이 없습니다. 예를 들어, 컴파일러에서 레그에 가장 중요한 3 비트 또는 가장 중요한 3 비트가 할당 될지 여부를 자유롭게 선택할 수 있습니다.
zvrba

3
비트 필드는 표준이 구현이 실제로 보완되는 방식에 많은 자유를 제공하는 곳의 예이며 실제로는 거의 쓸모가 없습니다. 값이 차지하는 비트 수와 저장 방법에 관심이있는 경우 비트 마스크를 사용하는 것이 좋습니다.
Mark Bessey

26
비트 필드는 "정수 조각"이 아닌 구조 요소로 취급하는 한 실제로 이식 가능합니다. 위치가 아닌 크기는 메모리가 제한된 임베디드 시스템에서 중요합니다. 각 비트는 소중합니다. 그러나 오늘날의 대부분의 코더는이를 기억하기에는 너무 어립니다. :-)
Adam Liss

5
@Adam : 바이트 내 비트 필드의 위치에 의존하는 경우 임베디드 시스템 (또는 다른 곳)에서 위치가 중요 할 수 있습니다. 마스크를 사용하면 모든 모호성이 제거됩니다. 노조도 마찬가지입니다.
Steve Melnikoff

37

C에는 표준이 있지만 모든 C 컴파일러가 완전히 호환되는 것은 아닙니다 (아직 완전한 C99 컴파일러는 아직 보지 못했습니다!).

즉, 내가 선호하는 트릭은 C 의미에 의존하기 때문에 플랫폼 전체에서 명백하고 이식성이없는 트릭입니다. 일반적으로 매크로 또는 비트 산술에 관한 것입니다.

예를 들어 : 임시 변수를 사용하지 않고 부호없는 정수 두 개를 교체합니다.

...
a ^= b ; b ^= a; a ^=b;
...

또는 다음과 같은 유한 상태 머신을 나타내는 "확장 C":

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

다음 매크로를 사용하면됩니다.

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

그러나 일반적으로 영리한 트릭은 마음에 들지 않지만 코드를 불필요하게 복잡하게 읽기 (스왑 예)로 만들고 코드를 더 명확하게하고 의도를 직접 전달하는 코드를 좋아합니다 (FSM 예와 같이). .


18
C는 연결을 지원하므로 ^ = b ^ = a ^ = b;
OJ.

4
엄밀히 말하면, 상태 예제는 C 언어가 아닌 전 처리기의 틱입니다. 후자는없이 전자를 사용할 수 있습니다.
Greg Whitfield

15
OJ : 실제로 제안하는 것은 시퀀스 포인트 규칙으로 인해 정의되지 않은 동작입니다. 대부분의 컴파일러에서 작동하지만 정확하거나 이식 가능하지 않습니다.
에반 테란

5
빈 레지스터의 경우 Xor 스왑은 실제로 효율성이 떨어질 수 있습니다. 적절한 최적화 프로그램은 임시 변수를 레지스터로 만듭니다. 구현 (및 병렬 처리 지원 필요)에 따라 스왑은 실제로 레지스터 대신 동일한 실제 메모리를 사용할 수 있습니다.
Paul de Vrieze

27
실제로 이렇게하지 마십시오 : en.wikipedia.org/wiki/…
Christian Oudard

37

더프 장치 와 같은 인터레이스 구조 :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

29
@ComSubVie, Duff 's Device를 사용하는 사람은 Duff 's Device를보고 Duff 's Device를 사용하면 코드가 1337이 될 것이라고 생각하는 스크립트 키디입니다. (1.) Duff 's Device는 최신 프로세서의 오버 헤드 제로가 없기 때문에 최신 프로세서의 성능 향상을 제공하지 않습니다. 다시 말해, 이것은 쓸모없는 코드입니다. (2.) 프로세서가 오버 헤드 제로 루프를 제공하지 않더라도 memcpy ()를 사용할 때 더프 장치를 부끄럽게하는 SSE / altivec / vector-processing과 같은 것이있을 것입니다. (3.) memcpy () 더프를하는 것이 유용하지 않다는 것을 언급 했습니까?
Trevor Boyd Smith

2
@ComSubVie, 죽음의 주먹 ( en.wikipedia.org/wiki/… )을 만나십시오
Trevor Boyd Smith

12
@ Trevor : 스크립트 키디 프로그램 8051과 PIC 마이크로 컨트롤러만이 맞습니까?
SF.

6
@Trevor Boyd Smith : Duff의 장치는 구식이지만 ComSubVie의 답변을 검증하는 역사적인 호기심입니다. 어쨌든 위키피디아에 인용하자면 : "버프 4.0의 많은 Duff 장치 인스턴스가 XFree86 서버에서 제거되었을 때 성능이 크게 향상되었습니다." ...
paercebal

2
Symbian에서는 빠른 픽셀 코딩을 위해 다양한 루프를 평가했습니다. 더프의 장치는 어셈블러에서 가장 빠릅니다. 따라서 오늘날에도 여전히 스마트 폰의 주요 ARM 코어와 관련이 있습니다.

33

나는 C99에 추가되고 오랫동안 gcc에서 지원되는 지정된 초기화 프로그램을 매우 좋아합니다.

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

어레이 초기화는 더 이상 위치에 의존하지 않습니다. FOO 또는 BAR의 값을 변경하면 배열 초기화가 자동으로 새 값에 해당합니다.


gcc가 오랫동안 지원 한 구문은 표준 C99 구문과 동일하지 않습니다.
Mark Baker

28

C99에는 멋진 순서 구조 초기화가 있습니다.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

익명 구조와 배열은 내가 가장 좋아하는 것입니다. (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

또는

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

링크 된 목록을 인스 턴 스하는 데에도 사용할 수 있습니다 ...


3
이 기능을 일반적으로 "복합 리터럴"이라고합니다. 익명 (또는 명명되지 않은) 구조는 멤버 이름이없는 중첩 구조를 지정합니다.
calandoa

내 GCC에 따르면 "ISO C90은 복합 리터럴을 금지합니다".
jmtd 2016 년

"ISO C99는 복합 리터럴을 지원합니다." "확장으로 GCC는 C89 모드와 C ++에서 복합 리터럴을 지원합니다"(dixit info gcc). 또한 GNU 확장으로서 GCC는 복합 리터럴 (초기화 기가 상수가 아니기 때문에 ISO C99에서는 불가능)에 의해 정적 저장 시간을 갖는 객체의 초기화를 허용합니다. "
PypeBros

24

gcc에는 내가 좋아하는 C 언어에 대한 여러 가지 확장이 있으며 여기 에서 찾을 수 있습니다 . 내가 좋아하는 것 중 일부는 함수 속성 입니다. 매우 유용한 예는 format 속성입니다. printf 형식 문자열을 사용하는 사용자 정의 함수를 정의하는 경우 사용할 수 있습니다. 이 함수 속성을 사용하면 gcc는 인수를 검사하여 형식 문자열과 인수가 일치하는지 확인하고 적절하게 경고 또는 오류를 생성합니다.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));

24

내가 처음봤을 때 "충격"된 (숨겨진) 기능은 printf에 관한 것입니다. 이 기능을 사용하면 형식 지정자 자체의 형식을 지정할 때 변수를 사용할 수 있습니다. 코드를 찾으면 더 잘 보일 것입니다.

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

* 문자는이 효과를 얻습니다.


24

글쎄 ... C 언어의 장점 중 하나는 이식성과 표준이라고 생각합니다. 따라서 현재 사용중인 구현에서 "숨겨진 트릭"을 발견 할 때마다 사용하려고하지 않습니다. 가능한 표준 및 휴대용 C 코드.


그러나 실제로 다른 컴파일러로 코드를 얼마나 자주 컴파일해야합니까?
Joe D

3
@Joe D가 Windows / OSX / Linux와 같은 크로스 플랫폼 프로젝트라면 아마도 약간은 물론 x86과 x86_64 등과 같은 다른 아치가있을 경우 ...
Pharaun

@JoeD 하나의 컴파일러 벤더와 기꺼이 결혼하는 매우 좁은 프로젝트에 있지 않는 한. 실제로 컴파일러를 전환하지 않아도되지만 해당 옵션을 열린 상태로 유지하려고합니다. 임베디드 시스템을 사용하면 항상 선택의 여지가있는 것은 아닙니다. AHS, ASS.
XTL

19

여기에서 이미 논의한 바와 같이 컴파일 시간 주장 .

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);

16

상수 문자열 연결

내가 알고있는 모든 컴파일러가 그것을 지원하기 때문에 답변에서 이미 그것을 보지 못하는 것에 놀랐지 만 많은 프로그래머들이 그것을 무시하는 것처럼 보입니다. 때로는 매크로를 쓸 때뿐만 아니라 정말 편리합니다.

현재 코드에있는 유스 케이스 : #define PATH "/some/path/"구성 파일에 있습니다 (실제로 makefile에 의해 설정됩니다). 이제 리소스를 열 파일 이름을 포함한 전체 경로를 만들고 싶습니다. 그냥 간다 :

fd = open(PATH "/file", flags);

끔찍하지만 매우 일반적입니다.

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

일반적인 끔찍한 해결책은 다음과 같습니다.

  • 세 배나 길다
  • 훨씬 덜 읽기 쉬운
  • 훨씬 느리게
  • 임의의 버퍼 크기 제한으로 설정하면 덜 강력합니다 (그러나 일정한 문자열을 반복하지 않으면 더 긴 코드를 사용해야합니다).
  • 더 많은 스택 공간 사용

1
더티`\`를 사용하지 않고 여러 소스 라인에서 문자열 상수를 분할하는 것도 유용합니다.
고인돌

15

글쎄, 나는 그것을 사용하지 않았으며, 나는 누군가에게 그것을 추천할지 확실하지 않지만 Simon Tatham의 공동 루틴 트릭에 대한 언급 없이이 질문이 불완전 할 것이라고 생각합니다 .


12

배열 또는 열거를 초기화 할 때 이니셜 라이저 목록의 마지막 항목 뒤에 쉼표를 넣을 수 있습니다. 예 :

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

코드를 자동으로 생성하는 경우 마지막 쉼표를 제거하지 않아도됩니다.


예를 들어 Eric은 "baz"를 추가 한 다음 George는 "boom"을 추가하는 다중 개발자 환경에서도 중요합니다. Eric이 다음 프로젝트 빌드를 위해 코드를 꺼내기로 결정한 경우 여전히 George의 변경 사항으로 컴파일됩니다. 멀티 브랜치 소스 코드 제어 및 겹치는 개발 일정에 매우 중요합니다.
Harold Bamford

열거 형은 C99 일 수 있습니다. 배열 이니셜 라이저 및 후행 쉼표는 K & R입니다.
페루 치오

일반 열거 형은 c89, AFAIK에있었습니다. 적어도 그들은 주변에 있었다.
XTL

12

구조 할당이 멋지다. 많은 사람들은 구조체가 가치라는 것을 깨닫지 못하고 memcpy()간단한 할당이 트릭을 수행 할 때 사용할 필요가 없습니다 .

예를 들어, 가상의 2D 그래픽 라이브러리를 고려하면 (정수) 화면 좌표를 나타내는 유형을 정의 할 수 있습니다.

typedef struct {
   int x;
   int y;
} Point;

이제 함수 인수에서 초기화 된 점을 작성하여 다음과 같이 리턴하는 함수를 작성하는 것과 같이 "잘못된"것처럼 보일 수있는 작업을 수행합니다.

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

구조체 할당을 사용하여 반환 값이 값으로 복사되는 한 (물론) 안전합니다.

Point origin;
origin = point_new(0, 0);

이런 식으로 평범한 표준 C로 매우 깨끗하고 객체 지향적 인 코드를 작성할 수 있습니다.


4
물론 이런 방식으로 큰 구조체를 둥글게 전달하면 성능에 영향을 미칩니다. 그것은 종종 유용하며 (실제로 많은 사람들이 당신이 할 수없는 것을 알고 있습니다) 포인터를 전달하는 것이 더 나은지 고려해야합니다.
Mark Baker

1
물론있을 있습니다. 컴파일러가 사용법을 감지하고 최적화하는 것도 가능합니다.
언 와인드

내용이 아닌 포인터 자체를 복사하므로 요소 중 하나라도 포인터 인 경우주의하십시오. 물론 memcpy ()를 사용하는 경우에도 마찬가지입니다.
Adam Liss

컴파일러는 전역 최적화를 수행 할 수 없으면 참조 별을 사용하여 전달하는이 값으로 변환을 최적화 할 수 없습니다.
Blaisorblade

아마도 C ++에서 표준은 복사본을 최적화하는 것을 허용합니다 (표준은 부작용이 발생하지 않을 수있는 복사 생성자를 의미하기 때문에 컴파일러가 그것을 구현하도록 허용해야 함). 대부분의 C ++ 컴파일러 이후 C 컴파일러이기도합니다. 컴파일러가이 최적화를 수행 할 가능성이 높습니다.
Joseph Garvin

10

이상한 벡터 인덱싱 :

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
훨씬 낫다 ... char c = 2 [ "Hello"]; (이 후에 c == 'l').
yrp

5
v [index] == * (v + index) 및 index [v] == * (index + v)
Ferruccio

17
질문과 같이 실제로 "항상"사용하지 말라고 알려주십시오.
Tryke

9

C 컴파일러는 여러 표준 중 하나를 구현합니다. 그러나 표준이 있다고해서 언어의 모든 측면이 정의 된 것은 아닙니다. 예를 들어, Duff의 장치 는 인기있는 '숨겨진'기능으로, 현대 컴파일러는 특수 기술 인식 코드를 사용하여 최적화 기법이이 자주 사용되는 패턴의 원하는 효과를 방해하지 않도록합니다.

일반적으로 숨겨진 기능이나 언어 트릭은 컴파일러가 사용하는 C 표준의 면도기 가장자리에서 실행될 때 권장하지 않습니다. 이러한 많은 트릭은 한 컴파일러에서 다른 컴파일러로 작동하지 않으며 종종 이러한 종류의 기능은 특정 제조업체의 한 버전의 컴파일러 제품군에서 다른 버전으로 실패합니다.

C 코드를 손상시킨 다양한 트릭은 다음과 같습니다.

  1. 컴파일러가 메모리에 구조체를 배치하는 방법에 의존합니다.
  2. 정수 / 부동의 엔디안 에 대한 가정 .
  3. 기능 ABI에 대한 가정.
  4. 스택 프레임이 성장하는 방향에 대한 가정.
  5. 명령문 내 실행 순서에 대한 가정.
  6. 함수 인수에서 명령문의 실행 순서에 대한 가정.
  7. short, int, long, float 및 double 유형의 비트 크기 또는 정밀도에 대한 가정.

프로그래머가 대부분의 C 표준에서 '컴파일러 의존적'동작으로 지정된 실행 모델에 대해 가정 할 때마다 발생하는 다른 문제 및 문제.


대부분의 문제를 해결하려면 플랫폼의 특성에 따라 가정을하고 각 플랫폼을 고유 한 헤더로 설명하십시오. 주문 실행은 예외입니다. 절대 그것에 의존하지 마십시오. 다른 아이디어에서, 각 플랫폼은 신뢰할만한 결정을 내려야합니다.
Blaisorblade

2
@Blaisorblade 더 좋은 점은 컴파일 타임 어설 션을 사용하여 가정이 위반되는 플랫폼에서 컴파일이 실패하는 방식으로 가정을 문서화하는 것입니다.
RBerteig

코드가 여러 플랫폼 (원래 의도)에서 작동하고 기능 매크로가 잘못 설정된 경우 컴파일 타임 어설 션이이를 포착하도록 둘 다 결합해야한다고 생각합니다. 예를 들어 함수 ABI에 대한 가정이 컴파일 타임 어설 션으로 확인할 수 있는지 확실하지 않지만 다른 (유효한) 대부분의 경우 (실행 순서 제외 ;-)) 가능해야합니다.
Blaisorblade

기능 ABI 점검은 테스트 스위트에 의해 처리되어야합니다.
고인돌

9

sscanf를 사용할 때 % n을 사용하여 계속 읽을 위치를 찾을 수 있습니다.

sscanf ( string, "%d%n", &number, &length );
string += length;

분명히 다른 답변을 추가 할 수 없으므로 여기에 두 번째 답변을 추가하겠습니다. "&&"및 "||" 조건부로 :

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

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

이 코드는 다음을 출력합니다 :

안녕하세요
ROFL

8

코드에서 중단 점을 설정하기 위해 INT (3) 사용


3
휴대용이라고 생각하지 않습니다. x86에서 작동하지만 다른 플랫폼은 어떻습니까?
Cristian Ciupitu

1
나는 모른다-당신은 그것에 대해 질문을 게시해야합니다
Dror Helper

2
좋은 기술이며 X86 전용입니다 (다른 플랫폼에서도 비슷한 기술이있을 수 있음). 그러나 이것은 C의 기능이 아닙니다. 비표준 C 확장 또는 라이브러리 호출에 따라 다릅니다.
Ferruccio 2016 년

1
GCC에는 __builtin_trap과 MSVC __debugbreak가 있으며 지원되는 모든 아키텍처에서 작동합니다.
Axel Gneiting

8

C에서 가장 좋아하는 "숨겨진"기능은 printf에서 % n을 사용하여 스택에 다시 쓰는 것입니다. 일반적으로 printf는 형식 문자열을 기반으로 스택에서 매개 변수 값을 팝하지만 % n은 다시 쓸 수 있습니다.

여기 3.4.2 섹션을 확인 하십시오 . 많은 불쾌한 취약점으로 이어질 수 있습니다.


링크가 더 이상 작동하지 않습니다. 실제로 사이트 자체가 작동하지 않는 것 같습니다. 다른 링크를 제공 할 수 있습니까?
thequark 2012 년

@thequark : "포맷 문자열 취약점"에 관한 기사는 약간의 정보를 포함합니다. (예 : crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ). 그러나 필드의 특성상 보안 웹 사이트 자체는 다소 색다른 것이며 실제 학술 기사는 구현하기가 어렵습니다.
Sridhar Iyer

8

열거 형을 사용한 컴파일 타임 가정 검사 : 바보 같은 예제이지만 컴파일 타임 구성 가능한 상수가있는 라이브러리에 실제로 유용 할 수 있습니다.

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};

2
깔끔한 +1 나는 Microsoft의 CompilerAssert 매크로를 사용했지만 당신도 나쁘지 않습니다. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter

1
열거 방법이 마음에 듭니다. 이전에 사용한 접근 방식은 데드 코드 제거를 활용했습니다. "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}"링크 시간까지 오류가 발생하지 않았지만 프로그래머는 오류 메시지를 통해 blorg가 woozled되었음을 알고 있습니다.
supercat

8

Gcc (c)에는 중첩 함수 선언 및 a :: b 형식의? : 연산자와 같이 활성화 할 수있는 몇 가지 재미있는 기능이 있습니다.이 함수는 a가 false가 아닌 경우 a를 반환합니다.


8

최근에 0 비트 필드를 발견했습니다.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

어떤 레이아웃을 제공합니다

000aaabb 0ccccddd

: 0없이;

0000aaab bccccddd

너비가 0 인 필드는 다음 비트 필드가 다음 원자 엔티티 ( char) 에 설정되어야 함을 나타냅니다.


7

C99 스타일 변수 인수 매크로, 일명

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

다음과 같이 사용됩니다

ERR(errCantOpen, "File %s cannot be opened", filename);

여기에서는 stringize 연산자와 문자열 상수 연결, 내가 정말 좋아하는 다른 기능도 사용합니다.


VA_ARGS에 여분의 'R'이 있습니다 .
Blaisorblade

6

가변 크기 자동 변수는 경우에 따라 유용합니다. 이들은 nC99에 추가되었으며 gcc에서 오랫동안 지원되었습니다.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

고정 크기 프로토콜 헤더와 가변 크기 데이터를위한 공간이있는 스택의 버퍼가 생깁니다. alloca ()와 동일한 효과를 얻을 수 있지만이 구문은 더 간결합니다.

이 루틴을 호출하기 전에 extraPadding이 합리적인 값인지 확인해야합니다. malloc 또는 다른 메모리 할당 기술을 호출하기 전에 인수를 확인해야하므로 실제로 드문 일이 아닙니다.


대상 플랫폼에서 바이트 / 문자의 너비가 정확히 8 비트가 아닌 경우에도 올바르게 작동합니까? 알아요, 그 경우는 드물지만 여전히 ... :)
Stephan202
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.