nonocast / me

记录和分享技术的博客
http://nonocast.cn
MIT License
20 stars 0 forks source link

学习 C/C++ (Part 14: define) #267

Open nonocast opened 2 years ago

nonocast commented 2 years ago

复习一下define语法:

编译一个C程序的第一个步骤被称为preprocessing(预处理)阶段。C preprocessor(预处理器)在源代码编译之前对其进行一系列的文本操作,包括删除注释、插入被#include指令包含的文件内容,定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。(# 开头的指令都是preprocessor专用的)

#define name stuff

通过define这条指令后,在预处理 (preprocessing)阶段时,preprocessor就会把name替换成stuff。define和指针一样,用不好就是一地鸡毛,用的好就是yyds。

我们从hello world开始,

main.c

#define NAME nonocast

int main() {
  return 0;
}

preprocessing: gcc -E main.c -o main.iclang -E main.c -o main.i, gcc的help中-E flag表示Only run the preprocessor

省略stdio.h的内容

int main() {
  printf("%f\n", 3.14);
  return 0;
}

String

#define URL "http://github.com/nonocast/me/issues"

int main() {
  printf("%s\n", URL);
  return 0;
}

Expression

#define AGE (20 / 2)

int main() {
  printf("%d\n", AGE);
  return 0;
}

预处理会保留括号: printf("%d\n", (20 / 2));, 所以这里的define也可以省略括号: #define AGE 20 / 2

Function

#define SQUARE(x) x * x

int main() {
  printf("%d\n", SQUARE(3));
  return 0;
}

替换后: printf("%d\n", 3 * 3);

挑战一下异常情况

#define DEBUG 1

int main() {
  DEBUG
  DEBUG
  DEBUG
  return 0;
}

预处理后是三行1,编译通不通过那是后面的事情,预处理是OK的。

#define DEBUG

int main() {
  DEBUG
  DEBUG
  DEBUG
  return 0;
}

这个就变成了:

int main()  {

  return 0;
}

ifdef

main.c

#include <stdio.h>
#define DEBUG

int main() {
#ifdef DEBUG
  printf("DEBUG MODE\n");
#endif

  return 0;
}

虽然刚才DEBUG没啥用,但通过ifdef就可以起到编译前控制,这个就很有用处了,比如在不同的平台或版本上采用不同的代码块。

通过undef可以remove掉这个定义。

#include <stdio.h>
#define DEBUG

int main() {
#ifdef DEBUG
  printf("DEBUG MODE\n");
#endif

#undef DEBUG

#ifdef DEBUG
  printf("UNREACHABLE\n");
#endif

  return 0;
}

第一个就不会起到作用,vscode也会自动帮你灰掉第二部分的代码块。

这里要区分一个场景,刚才ifdef/ifndef是通过检查是否定义,而不看这个值,那么#if就是用来做条件编译,仔细区分。

#include <stdio.h>
#define FEATURE_1 1
#define FEATURE_2 0
#define FEATURE_3 1

int main() {
#if FEATURE_1
  printf("FEATURE 1: ON\n");
#elif FEATURE_2
  printf("FEATURE 2: ON\n");  // did not show
#elif FEATURE_3
  printf("FEATURE 3: ON\n");
#endif

  return 0;
}

gcc/clang D flag

gcc/clang通过时可以外部注入define,这个真是上帝视角,

#include <stdio.h>

int main() {
#ifdef DEBUG
  printf("%s\n", URL);
#endif

  return 0;
}

然后通过clang -DDEBUG -DNAME='"nonocast"' -E main.c注入,预处理后的如下:

int main() {
  printf("%s\n", "nonocast");
  return 0;
}

Makefile

main.c

#include <stdio.h>

int main() {
#ifdef DEBUG
  printf("%s\n", NAME);
#endif

  printf("Version: %s\n", VERSION);

  return 0;
}

Makefile

VERSION=v1.3
CC=clang
CFLAGS=-arch arm64 `pkg-config --cflags openssl`
LDFLAGS=`pkg-config --libs openssl`
DEF=-DVERSION=\"$(VERSION)\" -DDEBUG -DNAME=\"nonocast\"

build: src/main.c
    $(CC) $(CFLAGS) $(LDFLAGS) $(DEF) -o build/app src/main.c

run: build
    ./build/app

CMake

cmake_minimum_required(VERSION 3.10)
project(hello)

add_compile_definitions(DEBUG)
add_compile_definitions(NAME=\"nonocast\")

add_executable(app src/main.c)

FILE

预处理器会偷偷夹带一些内置的define

#include <stdio.h>

int main() {
  printf("file: %s\n", __FILE__); // main.c
  printf("function: %s\n", __FUNCTION__); // main
  printf("line: %d\n", __LINE__); // 5
  printf("date: %s\n", __DATE__); // May 15 2022
  printf("time: %s\n", __TIME__); // 01:37:22/

  return 0;
}

参考阅读