답변:
함수 포인터. 함수 포인터 테이블을 사용하여 빠른 간접 스레드 코드 인터프리터 (FORTH) 또는 바이트 코드 디스패처를 구현하거나 OO와 유사한 가상 메소드를 시뮬레이션 할 수 있습니다.
그런 다음 qsort (), bsearch (), strpbrk (), strcspn ()과 같은 표준 라이브러리에 숨겨진 gem이 있습니다.
C의 잘못된 기능은 부호있는 산술 오버플로가 정의되지 않은 동작 (UB)이라는 것입니다. 따라서 부호있는 정수 인 x + y와 같은 표현식이 나타날 때마다 오버플로가 발생하여 UB가 발생할 수 있습니다.
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();
...
}
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
이들은 표준에서 선택적인 항목이지만 사람들이 끊임없이 재정의하고 있기 때문에 숨겨진 기능이어야합니다. 내가 작업 한 코드베이스 (현재는 여전히)에는 여러 식별자가있는 여러 재정의가 있습니다. 대부분의 경우 전 처리기 매크로를 사용합니다.
#define INT16 short
#define INT32 long
등등. 머리카락을 뽑고 싶어요. 괴물 표준 정수 타입 정의를 사용하십시오!
쉼표 연산자는 널리 사용되지 않습니다. 확실히 남용 될 수 있지만 매우 유용 할 수도 있습니다. 이 사용이 가장 일반적입니다.
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
그러나이 연산자는 어디에서나 사용할 수 있습니다. 관찰 :
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
각 명령문은 평가되지만 표현식의 값은 마지막으로 평가 된 명령문의 값입니다.
구조를 0으로 초기화
struct mystruct a = {0};
이것은 모든 구조 요소를 0으로 만듭니다.
memset
/ calloc
"모든 바이트를 0으로"(물리적으로 0)를 수행하십시오. 실제로 모든 유형에 대해 정의 된 것은 아닙니다. 적절한 논리적 제로 값으로 모든 것을{ 0 }
강화 합니다. 예를 들어 포인터는 주어진 플랫폼의 null 값이 인 경우에도 적절한 null 값을 얻도록 보장됩니다 . 0xBAADFOOD
memset
( 0
두 번째 주장으로). 당신은 얻을 논리적 당신이 / 할당 초기화 할 때 제로 0
(또는 { 0 }
소스 코드에서 객체). 이 두 종류의 0은 반드시 동일한 결과를 생성하지는 않습니다. 포인터가있는 예에서와 같이. 당신이 할 때 memset
포인터에, 당신은 얻을 0x0000
포인터를. 그러나 0
포인터 를 할당 하면 물리적 포인터0xBAADF00D
또는 기타 값 이 될 수있는 null 포인터 값 이 표시됩니다.
double
. 일반적으로 논리적 영점과 물리적 영점이 동일한 IEEE-754 표준에 따라 구현됩니다. 그러나이 언어에는 IEEE-754가 필요하지 않습니다. 따라서 double d = 0;
(논리적 0) 할 때 실제로 메모리에서 차지하는 일부 비트가 0이 아닌 경우가 발생할 d
수 있습니다.
다중 문자 상수 :
int x = 'ABCD';
이 세트 x
에 0x41424344
(또는 0x44434241
, 아키텍처에 따라 다름).
편집 : 이 기술은 특히 int를 직렬화하는 경우 이식성이 없습니다. 그러나 자체 문서화 열거 형을 만드는 것이 매우 유용 할 수 있습니다. 예 :
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
따라서 원시 메모리 덤프를보고 열거 형 값을 조회하지 않고도 값을 결정해야하는 경우 훨씬 간단 해집니다.
나는 비트 필드를 사용한 적이 없지만 초저 레벨의 물건에는 멋지게 들립니다.
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)
.
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 예와 같이). .
더프 장치 와 같은 인터레이스 구조 :
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);
}
}
나는 C99에 추가되고 오랫동안 gcc에서 지원되는 지정된 초기화 프로그램을 매우 좋아합니다.
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
어레이 초기화는 더 이상 위치에 의존하지 않습니다. FOO 또는 BAR의 값을 변경하면 배열 초기화가 자동으로 새 값에 해당합니다.
익명 구조와 배열은 내가 가장 좋아하는 것입니다. (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});
링크 된 목록을 인스 턴 스하는 데에도 사용할 수 있습니다 ...
gcc에는 내가 좋아하는 C 언어에 대한 여러 가지 확장이 있으며 여기 에서 찾을 수 있습니다 . 내가 좋아하는 것 중 일부는 함수 속성 입니다. 매우 유용한 예는 format 속성입니다. printf 형식 문자열을 사용하는 사용자 정의 함수를 정의하는 경우 사용할 수 있습니다. 이 함수 속성을 사용하면 gcc는 인수를 검사하여 형식 문자열과 인수가 일치하는지 확인하고 적절하게 경고 또는 오류를 생성합니다.
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
글쎄 ... C 언어의 장점 중 하나는 이식성과 표준이라고 생각합니다. 따라서 현재 사용중인 구현에서 "숨겨진 트릭"을 발견 할 때마다 사용하려고하지 않습니다. 가능한 표준 및 휴대용 C 코드.
여기에서 이미 논의한 바와 같이 컴파일 시간 주장 .
//--- 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);
상수 문자열 연결
내가 알고있는 모든 컴파일러가 그것을 지원하기 때문에 답변에서 이미 그것을 보지 못하는 것에 놀랐지 만 많은 프로그래머들이 그것을 무시하는 것처럼 보입니다. 때로는 매크로를 쓸 때뿐만 아니라 정말 편리합니다.
현재 코드에있는 유스 케이스 : #define PATH "/some/path/"
구성 파일에 있습니다 (실제로 makefile에 의해 설정됩니다). 이제 리소스를 열 파일 이름을 포함한 전체 경로를 만들고 싶습니다. 그냥 간다 :
fd = open(PATH "/file", flags);
끔찍하지만 매우 일반적입니다.
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
일반적인 끔찍한 해결책은 다음과 같습니다.
배열 또는 열거를 초기화 할 때 이니셜 라이저 목록의 마지막 항목 뒤에 쉼표를 넣을 수 있습니다. 예 :
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
코드를 자동으로 생성하는 경우 마지막 쉼표를 제거하지 않아도됩니다.
구조 할당이 멋지다. 많은 사람들은 구조체가 가치라는 것을 깨닫지 못하고 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로 매우 깨끗하고 객체 지향적 인 코드를 작성할 수 있습니다.
C 컴파일러는 여러 표준 중 하나를 구현합니다. 그러나 표준이 있다고해서 언어의 모든 측면이 정의 된 것은 아닙니다. 예를 들어, Duff의 장치 는 인기있는 '숨겨진'기능으로, 현대 컴파일러는 특수 기술 인식 코드를 사용하여 최적화 기법이이 자주 사용되는 패턴의 원하는 효과를 방해하지 않도록합니다.
일반적으로 숨겨진 기능이나 언어 트릭은 컴파일러가 사용하는 C 표준의 면도기 가장자리에서 실행될 때 권장하지 않습니다. 이러한 많은 트릭은 한 컴파일러에서 다른 컴파일러로 작동하지 않으며 종종 이러한 종류의 기능은 특정 제조업체의 한 버전의 컴파일러 제품군에서 다른 버전으로 실패합니다.
C 코드를 손상시킨 다양한 트릭은 다음과 같습니다.
프로그래머가 대부분의 C 표준에서 '컴파일러 의존적'동작으로 지정된 실행 모델에 대해 가정 할 때마다 발생하는 다른 문제 및 문제.
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
코드에서 중단 점을 설정하기 위해 INT (3) 사용
C에서 가장 좋아하는 "숨겨진"기능은 printf에서 % n을 사용하여 스택에 다시 쓰는 것입니다. 일반적으로 printf는 형식 문자열을 기반으로 스택에서 매개 변수 값을 팝하지만 % n은 다시 쓸 수 있습니다.
열거 형을 사용한 컴파일 타임 가정 검사 : 바보 같은 예제이지만 컴파일 타임 구성 가능한 상수가있는 라이브러리에 실제로 유용 할 수 있습니다.
#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)
};
#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
)
Gcc (c)에는 중첩 함수 선언 및 a :: b 형식의? : 연산자와 같이 활성화 할 수있는 몇 가지 재미있는 기능이 있습니다.이 함수는 a가 false가 아닌 경우 a를 반환합니다.
최근에 0 비트 필드를 발견했습니다.
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
어떤 레이아웃을 제공합니다
000aaabb 0ccccddd
: 0없이;
0000aaab bccccddd
너비가 0 인 필드는 다음 비트 필드가 다음 원자 엔티티 ( char
) 에 설정되어야 함을 나타냅니다.
C99 스타일 변수 인수 매크로, 일명
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
다음과 같이 사용됩니다
ERR(errCantOpen, "File %s cannot be opened", filename);
여기에서는 stringize 연산자와 문자열 상수 연결, 내가 정말 좋아하는 다른 기능도 사용합니다.
가변 크기 자동 변수는 경우에 따라 유용합니다. 이들은 nC99에 추가되었으며 gcc에서 오랫동안 지원되었습니다.
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
고정 크기 프로토콜 헤더와 가변 크기 데이터를위한 공간이있는 스택의 버퍼가 생깁니다. alloca ()와 동일한 효과를 얻을 수 있지만이 구문은 더 간결합니다.
이 루틴을 호출하기 전에 extraPadding이 합리적인 값인지 확인해야합니다. malloc 또는 다른 메모리 할당 기술을 호출하기 전에 인수를 확인해야하므로 실제로 드문 일이 아닙니다.