C 언어 프로그래밍 환경 및 전처리

1. 프로그램 번역 환경 및 실행 환경

ANSI C 구현에는 두 가지 다른 환경이 있습니다.
첫 번째는 소스 코드가 실행 가능한 기계 명령어로 변환되는 번역 환경입니다.
두 번째 유형은 실제로 코드를 실행하는 데 사용되는 실행 환경입니다.

2. 컴파일+링크에 대한 자세한 설명

2.1 번역 환경

여기에 이미지 설명을 삽입하세요.
프로그램을 구성하는 각 소스 파일은 컴파일 과정을 거쳐 목적 코드로 변환됩니다.
링커는 각 개체 파일을 함께 묶어 하나의 완전한 실행 프로그램을 형성합니다.
링커는 또한 프로그램에서 사용하는 표준 C 함수 라이브러리의 모든 함수를 소개하고 프로그래머의 개인
라이브러리를 검색할 수 있습니다. 필요한 함수는 다음과 같습니다. 프로그램에도 연결됩니다.
2.2 컴파일 자체도 여러 단계로 나뉩니다: '
코드 보기:
sum.c

int g_val = 2016;
void print(const char *str)
{
    
    
 printf("%s\n", str);
}

test.c

#include <stdio.h>
int main()
{
    
    
 extern void print(char *str);
 extern int g_val;
 printf("%d\n", g_val);
 print("hello bit.\n");
 return 0;
}

여기에 이미지 설명을 삽입하세요.
컴파일하는 동안 각 단계에서 어떤 일이 발생했는지 어떻게 확인할 수 있나요?
test.c

#include <stdio.h>
int main()
{
    
    
 int i = 0;
 for(i=0; i<10; i++)
 {
    
    
 printf("%d ", i);
 }
 return 0;
}

1. 전처리 옵션 gcc -E test.c -o test.i
전처리가 완료된 후 중지하고 전처리 후 생성된 결과는 파일 내 test.i에 저장됩니다. .
컴파일 옵션 gcc -S test.c
2. 컴파일이 완료된 후 중지하고 결과는 test.s에 저장됩니다.
어셈블리 gcc -c test.c
3. 어셈블리가 완료된 후 중지하고 결과가 test.o에 저장됩니다.
2.3 실행 환경
프로그램 실행 프로세스:
1. 프로그램이 메모리에 로드되어야 합니다. 운영 체제가 있는 환경: 이는 일반적으로 운영 체제에 의해 수행됩니다. 독립 실행형 환경에서는 프로그램 로딩을 수동으로 정렬해야 하거나 실행 가능한 코드를 읽기 전용 메모리에 배치하여 수행할 수 있습니다.
2. 프로그램 실행이 시작됩니다. 그런 다음 메인 함수를 호출합니다.
3. 프로그램 코드 실행을 시작합니다. 이때 프로그램은 런타임 스택을 사용하여 지역 변수와 함수의 반환 주소를 저장합니다. 프로그램은 정적 메모리를 사용할 수도 있는데, 정적 메모리에 저장된 변수는 프로그램이 실행되는 동안 해당 값을 유지합니다.
4. 프로그램을 종료합니다. 기본 기능을 정상적으로 종료합니다. 예기치 않게 종료될 수도 있습니다. ,

3. 전처리에 대한 자세한 설명

3.1 사전 정의된 기호

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

미리 정의된 기호는 언어에 내장되어 있습니다.
예:

printf("file:%s line:%d\n", __FILE__, __LINE__);

3.2 #정의

3.2.1 #define 식별자 정의

语法:
 #define name stuff

예를 들어:

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,       \
__DATE__,__TIME__ ) 

3.2.2 #define 매크로 정의

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。

다음은 매크로를 선언하는 방법입니다.
#define name( parament-list ) stuff
여기서 parament-list는 쉼표로 구분됩니다. 물건에 나타날 수 있는 기호 테이블입니다.
참고:
매개변수 목록의 왼쪽 대괄호는 이름 바로 옆에 있어야 합니다.
둘 사이에 공백이 있으면 매개변수 목록은 항목의 일부로 해석됩니다.
예:

#define SQUARE( x ) x * x

이 매크로는 x 매개변수를 받습니다.
위 선언 뒤에 를 입력하면

SQUARE( 5 );

프로그램에 배치되면 전처리기는 위의 표현식을 다음 표현식으로 바꿉니다.

5 * 5

경고:
이 매크로에 문제가 있습니다.
다음 코드 스니펫을 확인하세요.

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

언뜻 보면 이 코드가 값 36을 인쇄할 것이라고 생각할 수도 있습니다.
실제로는 11이 인쇄됩니다.
이유는 무엇입니까?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

이는 대체에 의해 생성된 표현식이 예상한 순서대로 평가되지 않음을 분명히 합니다.
매크로 정의에 두 개의 괄호를 추가하면 이 문제를 쉽게 해결할 수 있습니다.

#define SQUARE(x) (x) * (x)

이 전처리는 예상되는 효과를 생성합니다.

printf ("%d\n",(a + 1) * (a + 1) );

다음은 또 다른 매크로 정의입니다.

#define DOUBLE(x) (x) + (x)

이전 문제를 피하기 위해 정의에 괄호를 사용했지만 이 매크로로 인해 새로운 오류가 발생할 수 있습니다.

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

이것은 어떤 값을 인쇄합니까?
경고:
100이 인쇄되는 것처럼 보이지만 실제로는 55가 인쇄됩니다.
교체 후 다음을 발견했습니다. < /span>

printf ("%d\n",10 * (5) + (5));

곱하기 연산이 매크로 정의 추가에 앞서기 때문에 문제 55
발생합니다. 해결 방법은 매크로 정의 표현식의 양쪽에 괄호 쌍을 추가하는 것입니다

#define DOUBLE( x)   ( ( x ) + ( x ) )

힌트:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中
的操作符或邻近操作符之间不可预料的相互作用。

2.2.3 #define 대체 규칙
'프로그램에서 #define 정의 기호 및 매크로를 확장할 때 여러 단계가 필요합니다.

  1. 매크로를 호출할 때 먼저 매개변수에 #define으로 정의된 기호가 포함되어 있는지 확인합니다. 그렇다면 먼저
    교체됩니다.
  2. 그러면 대체 텍스트가 프로그램의 원본 텍스트 위치에 삽입됩니다. 매크로의 경우 매개변수 이름은 해당 값으로 대체됩니다.
  3. 마지막으로 결과 파일을 다시 검사하여 #define으로 정의된 기호가 포함되어 있는지 확인합니다. 그렇다면 위의 과정을 반복하세요. 참고:

  4. 다른 #define으로 정의된 기호는 매크로 매개변수 및 #define 정의에 나타날 수 있습니다. 그러나 매크로의 경우 재귀가 발생할 수 없습니다.
  5. 전처리기가 #define 기호를 검색할 때 문자열 상수의 내용은 검색되지 않습니다.

3.2.4 #과 ##

문자열에 매개변수를 삽입하는 방법은 무엇입니까?
먼저 다음 코드를 살펴보겠습니다.

char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

여기에 출력이 있나요
안녕하세요?
대답은 '그렇다'입니다.
문자열이 자동 연결의 특성을 가지고 있음을 발견했습니다.

  1. 그럼 이런 코드를 작성할 수 있을까요? :
#define PRINT(FORMAT, VALUE)\
 printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10);

여기서 문자열은 매크로 매개변수로 사용되는 경우에만 문자열에 배치될 수 있습니다.

  1. 또 다른 요령은 다음과 같습니다.
    #을 사용하여 매크로 매개변수를 해당 문자열로 변환합니다.
    예를 들어
int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);//产生了什么效果?

코드의 #VALUE는 전처리기에 의해 다음과 같이 처리됩니다.
"VALUE" .
최종 출력 결과는 다음과 같습니다.

the value of i+3 is 13

##의 역할
##은 양쪽 기호를 하나의 기호로 결합할 수 있습니다.
매크로 정의를 통해 별도의 텍스트 조각에서 식별자를 생성할 수 있습니다.

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

참고:
이러한 연결은 유효한 식별자를 생성해야 합니다. 그렇지 않으면 결과가 정의되지 않습니다.
3.2.5 부작용이 있는 매크로 매개변수
매크로 정의에 매크로 매개변수가 두 번 이상 나타날 때 해당 매개변수에 부작용이 있는 경우 사용 중입니다. 이 매크로는
위험할 수 있으며 예측할 수 없는 결과를 초래할 수 있습니다. 부작용은 표현식을 평가할 때 발생하는 영구적인 효과입니다.
예를 들어

x+1;//不带副作用
x++;//带有副作用

MAX 매크로는 부작용이 있는 매개변수로 인해 발생한 문제를 보여줄 수 있습니다.

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

여기서 우리는 전처리기 처리 후의 결과가 무엇인지 알아야 합니다.

z = ( (x++) > (y++) ? (x++) : (y++));

따라서 출력은 다음과 같습니다.

x=6 y=10 z=9

3.2.6 매크로와 함수 비교
매크로는 일반적으로 간단한 작업을 수행하는 데 사용됩니다.
예를 들어 두 숫자 중 더 큰 숫자를 찾습니다.

#define MAX(a, b) ((a)>(b)?(a):(b))

그렇다면 이 작업을 수행하기 위해 함수를 사용하는 것은 어떨까요?
두 가지 이유가 있습니다.

  1. 함수를 호출하고 반환하는 데 사용되는 코드는 이 작은 계산 작업을 실제로 수행하는 데 걸리는 시간보다 더 많은 시간이 걸릴 수 있습니다.
    따라서 프로그램 크기나 속도 측면에서 매크로가 함수보다 낫습니다.
  2. 더 중요한 것은 함수의 매개변수가 특정 유형으로 선언되어야 한다는 것입니다.
    따라서 이 함수는 적절한 유형의 표현식에만 사용할 수 있습니다. 반대로
    and>와 비교할 수 있는 정수, 긴 정수, 부동 소수점 유형 등에 이 매크로를 어떻게 적용할 수 있을까요?
    매크로는 유형 독립적입니다.
    매크로의 단점: 물론 매크로에는 기능에 비해 단점도 있습니다.
    1. 매크로를 사용할 때마다 매크로 정의 코드의 복사본이 생성됩니다. 프로그램에 삽입됩니다. 매크로가 상대적으로 짧지 않으면 프로그램 길이가 크게 늘어날 수 있습니다.
  3. 매크로는 디버깅할 수 없습니다.
  4. 매크로는 유형 독립적이기 때문에 충분히 엄격하지 않습니다.
  5. 매크로는 연산자 우선순위 문제를 발생시켜 프로그램에 오류가 발생하기 쉽습니다.

매크로는 때때로 함수가 할 수 없는 일을 할 수 있습니다. 예를 들어, 매크로 매개변수에는 유형이 있을 수 있지만 함수에는 유형이 있을 수 없습니다.

#define MALLOC(num, type)\
 (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

매크로와 함수의 비교

여기에 이미지 설명을 삽입하세요.
3.2.7 명명 규칙
일반적으로 함수 매크로를 사용하는 구문은 매우 유사합니다. 따라서 언어 자체는 우리가 둘을 구별하는 데 도움을 줄 수 없습니다.
그렇다면 우리의 일반적인 습관 중 하나는 다음과 같습니다.

把宏名全部大写
函数名不要全部大写

3.3 ​​​​#undef
이 명령은 매크로 정의를 제거하는 데 사용됩니다.

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

3.4 명령줄 정의
많은 C 컴파일러는 명령줄에서 기호를 정의하는 기능을 제공합니다. 컴파일 프로세스를 시작하는 데 사용됩니다.
예: 이 기능은 동일한 소스 파일을 기반으로 다양한 버전의 프로그램을 컴파일하려는 경우에 유용합니다. (프로그램에서 특정 길이의 배열을 선언
한다고 가정합니다. 머신 메모리가 제한되어 있으면 아주 작은 배열이 필요하지만 또 다른
머신 메모리가 필요합니다. 대문자이므로 대문자일 수 있는 배열이 필요합니다.)

#include <stdio.h>
int main()
{
    
    
    int array [ARRAY_SIZE];
    int i = 0;
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
    
    
        array[i] = i;
   }
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
    
    
        printf("%d " ,array[i]);
   }
    printf("\n" );
    return 0;
}

컴파일 지침:

//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c

3.5 조건부 컴파일
프로그램을 컴파일할 때 명령문(명령문 그룹)을 컴파일하거나 폐기하려는 경우 매우 편리합니다. 조건부 컴파일 지시문이 있기 때문입니다.
예:

#include <stdio.h>
#define __DEBUG__
int main()
{
    
    
 int i = 0;
 int arr[10] = {
    
    0};
 for(i=0; i<10; i++)
 {
    
    
 arr[i] = i;
 #ifdef __DEBUG__
 printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 
 #endif //__DEBUG__
 }
 return 0;
}

일반적인 조건부 컴파일 지시문:

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

3.6 파일 포함
우리는 #include 지시어가 다른 파일을 컴파일하게 할 수 있다는 것을 이미 알고 있습니다. 이 교체는 #include 지시어가 실제로 나타나는 위치에서와 마찬가지로 간단하게 작동합니다.
전처리기는 먼저 이 지시어를 제거하고 포함된 파일의 내용으로 바꿉니다.
이러한 소스 파일이 10번 포함되면 실제로는 10번 컴파일됩니다.
3.6.1 헤더 파일 포함 방법:
로컬 파일 포함

#include "filename"

검색 전략: 먼저 소스 파일이 있는 디렉터리에서 검색하고, 헤더 파일이 없으면 컴파일러는 라이브러리 함수 헤더 파일을 검색하는 것과 마찬가지로 지정된 위치에서 헤더 파일을 검색합니다.
.
찾을 수 없으면 컴파일 오류가 표시됩니다.
라이브러리 파일에 포함

#include <filename.h>

표준 경로에서 헤더 파일을 직접 검색하여 찾지 못한 경우 컴파일 오류가 발생합니다.
이는 라이브러리 파일도 "" 형식으로 포함될 수 있다는 의미입니까?
대답은 '예'입니다. 가능합니다.
하지만 검색 효율이 떨어지죠.물론 라이브러리 파일인지 로컬 파일인지 구별하기가 쉽지 않습니다.

4. 기타 전처리 지침

#error
#pragma
#line
....................
//还有很多不做介绍了
//可以参考《C语言深度解剖》学习

이 장의 끝…

Supongo que te gusta

Origin blog.csdn.net/HD_13/article/details/133950021
Recomendado
Clasificación