C/C++文档阅读笔记-A Simple Makefile Tutorial解析
Posted IT1995
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++文档阅读笔记-A Simple Makefile Tutorial解析相关的知识,希望对你有一定的参考价值。
Makefile文件可以使得程序编译变得简单。本博文并不是很系统的讲解makefile,本博文的目标是让读者快速编写自己的makefile文件并能应用到中小项目中。
简单实例
举个例子有下面3个文件,分别是hellomake.c,hellofunc.c,hellomake.h这3个文件构成了一个基本的带有main函数的程序。
hellomake.c | hellofunc.c | hellomake.h |
#include <hellomake.h> int main() //call a function in another file myPrintHelloMake(); return(0); | #include <stdio.h> #include <hellomake.h> void myPrintHelloMake(void) printf("Hello makefiles!\\n"); return; | /* example include file */ void myPrintHelloMake(void); |
通常在不使用makefile时,使用如下的命令编译程序:
gcc -o hellomake hellomake.c hellofunc.c -I.
此命令会编译2个.c文件,包括hellomake.c和hellofunc.c并生成hellomake这个可执行程序。-I . 是告知gcc去当前目录(.)找.h文件,也就是找hellomake.h。没用makefile文件,程序员对代码进行修改后,就需要不停按键盘的↑键去找对应的命令进行编译,如果新增了一个.c文件,还需要修改对应的命令。
所以这种方式就存在2个缺点:
1.如果电脑重启,或者新增了.c文件,就需要重新敲边缘命令,这样十分不方便;
2.如果仅仅只修改了一个.c文件,使用编译命令就会把项目所有的.c重新编译,这样编译就会变得十分慢。
创建一个最简单的makefile文件
Makefile1
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
文件名可以叫Makefile或makefile,在命名行中输入make执行编译命令,这个make是不需要带参数的。
第一行的hellomake只是一个tab,后面*.c是文件名。关键的命令是在第2行,程序员通常写的makefile,执行make命令时当选择的文件有一个被修改了,才会执行。
Makefile1解决开发时不停使用键盘↑键带来的操作不方便的问题,但他属于把所有文件都编译,所以没有解决效率问题。
注意:在makefile的首行“:”号的前面都要有个tab,这个是必须存在的。
为了提高编译效率,就有了下面这个Makefile2:
Makefile2
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
上面定义了CC和CFLAGS两个宏,目的是方便makefile后面的脚本使用,CC=gcc并且后面的$(CC) -o hellomake hellomake.o hellofunc.o说明这个CC=gcc是使用C编译器,CFLAGS列出来标签的list, -I . 编译成.o文件需要依赖当前目录的.h文件。make命令首先会编译每一个.c文件,最后构建成可执行的hellomake文件。
上面这种方式已经可以用于小项目了,但是如果hellomake.h文件改变了,make命令也不会去重编译.c文件。对于这种情况,需要告诉make,哪个.h文件改变了,需要重新编译,这里就有了Makefile3。
Makefile3
CC=gcc
CFLAGS=-I .
DEPS=hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
新建了DEPS宏,这个宏里面包含了.cpp代码中需要关注的.h文件,这里需要注意的是,make生成object文件就需要检测这个.h文件是否改变。下面是各个符号的含义:
%.o:当前目录匹配到所有.o结尾的文件;
%.c:当前目录匹配到所有.c结尾的文件;
-c:生成对应的object文件;
-o $@:编译时进行输出,输出时文件的名字放到“:”的左边;
$<:依赖的第一个文件;
下面对上面的Makefile3进行简化,使用了$@和$^。对编译规则重写进行了编写,在下面的示例中所有的include文件都在DEPS宏中,所有的object文件都在OBJ宏中列出。
Makefile4
CC=gcc
CFLAGS=-I .
DEPS=hellomake.h
OBJ=hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
对符合解释的补充:
$^:所有依赖文件
如果要将.h文件放到include目录,source文件放到src目录,库文件放到lib目录。.o文件也要规范位置,这样就需要编写新的makefile文件,举个例子当下面这个程序依赖m.so库,这个m是math的意思,并且还需要编写make clean的规则,这样的makefile可以这样写:
Makefile5
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
解释下参数:
patsubst:
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。
这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。
(可以用“\\”来转义,以“\\%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o, a.c b.c)
把字串“a.c b.c”符合模式[%.c]的单词替换成[%.o],返回结果是“a.o b.o”
实例代码打包下载地址:
https://github.com/fengfanchen/CAndCPP/tree/master/MakeFileExample
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 1.)(python/c/c++版)(笔记)
原文:Let’s Build A Simple Interpreter. Part 1.
文章目录
【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 1.)(python/c/c++版)(part 1)
pascal代码,我们要为这种代码做一个解释器
program factorial;
function factorial(n: integer): longint;
begin
if n = 0 then
factorial := 1
else
factorial := n * factorial(n - 1);
end;
var
n: integer;
begin
for n := 0 to 16 do
writeln(n, '! = ', factorial(n));
end.
首先我们先来实现pascal编译器的一个小功能,两个数的加法,我们准备用python实现,但是如果你想用其他语言实现也是可行的,这是它的代码:
# -*- coding: utf-8 -*-
"""
@File : 1.py
@Time : 2021/5/19 14:44
@Author : Dontla
@Email : sxana@qq.com
@Software: PyCharm
"""
# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF'
class Token(object):
def __init__(self, type_, value):
# token type: INTEGER, PLUS, or EOF
self.type = type_
# token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', or None
self.value = value
def __str__(self):
"""String representation of the class instance.
Examples:
Token(INTEGER, 3)
Token(PLUS '+')
"""
return 'Token({type}, {value})'.format(
type=self.type,
value=repr(self.value)
)
def __repr__(self):
return self.__str__()
class Interpreter(object):
def __init__(self, text):
# 客户端字符串输入, 比如 "3+5"
self.text = text
# self.pos is an index into self.text
self.pos = 0
# current token instance(当前标记实例)
self.current_token = None
def error(self):
raise Exception('Error parsing input') # 语法分析输入出错
def get_next_token(self):
"""Lexical analyzer (also known as scanner or tokenizer)
This method is responsible for breaking a sentence
apart into tokens. One token at a time.
词法分析器(也称为扫描器scanner或标记器tokenizer)
这个方法负责将一个句子分解成标记tokens。一次一个标记
"""
text = self.text
# is self.pos index past the end of the self.text ?
# if so, then return EOF token because there is no more
# input left to convert into tokens
# self.pos索引是否超过self.text的结尾?
# 如果是,则返回EOF标记,因为没有更多的标记
# 向左输入以转换为标记
if self.pos > len(text) - 1:
return Token(EOF, None)
# get a character at the position self.pos and decide
# what token to create based on the single character
# 在self.pos位置获取一个字符,并根据单个字符决定要创建的标记
current_char = text[self.pos]
# if the character is a digit then convert it to
# integer, create an INTEGER token, increment self.pos
# index to point to the next character after the digit,
# and return the INTEGER token
# 如果字符是数字,则将其转换为整型,创建整型标记,增加self.pos索引以指向数字后面的下一个字符,然后返回整型标记
if current_char.isdigit(): # isdigit()函数,全是数字返回True,否则返回False
token = Token(INTEGER, int(current_char)) # 创建一个token
self.pos += 1
return token
if current_char == '+':
token = Token(PLUS, current_char)
self.pos += 1
return token
self.error()
def eat(self, token_type):
# compare the current token type with the passed token
# type and if they match then "eat" the current token
# and assign the next token to the self.current_token,
# otherwise raise an exception.
if self.current_token.type == token_type:
self.current_token = self.get_next_token()
else:
self.error()
def expr(self):
"""expr -> INTEGER PLUS INTEGER"""
# set current token to the first token taken from the input
self.current_token = self.get_next_token()
# we expect the current token to be a single-digit integer
left = self.current_token
self.eat(INTEGER)
# we expect the current token to be a '+' token
op = self.current_token
self.eat(PLUS)
# we expect the current token to be a single-digit integer
right = self.current_token
self.eat(INTEGER)
# after the above call the self.current_token is set to
# EOF token
# at this point INTEGER PLUS INTEGER sequence of tokens
# has been successfully found and the method can just
# return the result of adding two integers, thus
# effectively interpreting client input
result = left.value + right.value
return result
def main():
while True:
try:
# 要在Python3下运行,请将“raw_input”调用替换为“input”
# text = raw_input('calc> ')
text = input('calc> ') # 获取键盘输入,参数为提示信息
except EOFError: # 不知是什么异常
break
if not text:
continue
interpreter = Interpreter(text)
result = interpreter.expr()
print(result)
if __name__ == '__main__':
main()
运行结果:
D:\\python_virtualenv\\my_flask\\Scripts\\python.exe C:/Users/Administrator/Desktop/新建文件夹/1.py
calc> 1+2
3
calc>
将输入字符串分解为token标记的过程称为词法分析(lexical analysis),词法分析器( lexical analyzer或 lexer )也叫扫描器(scanner )或标记器(tokenizer)。
>>> from calc1 import Interpreter
>>>
>>> interpreter = Interpreter('3+5')
>>> interpreter.get_next_token()
Token(INTEGER, 3)
>>>
>>> interpreter.get_next_token()
Token(PLUS, '+')
>>>
>>> interpreter.get_next_token()
Token(INTEGER, 5)
>>>
>>> interpreter.get_next_token()
Token(EOF, None)
>>>
让我们回顾一下您的解释器如何评估算术表达式:
- 解释器接受一个输入字符串,比如说“3+5”
- 解释器调用expr方法在词法分析器get_next_token返回的标记流中查找结构。它试图找到的结构是INTEGER PLUS INTEGER的形式。在确认结构后,它通过添加两个INTEGER标记的值来解释输入,因为此时解释器很清楚它需要做的是添加两个整数 3 和 5。
检查理解:
什么是解释器interpreter?
什么是编译器compiler?
解释器和编译器有什么区别?
什么是标记token?
将输入分解为标记的过程的名称是什么?
进行词法分析lexical analysis的解释器的部分是什么?
解释器或编译器的那部分的其他常见名称是什么?
用c/c++实现
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
struct Interpreter
{
char* text;
int pos;
struct Token (*get_next_token)(struct Interpreter*);
};
struct Token
{
int type;
char value;
};
struct Token get_next_token(struct Interpreter* pipt) {
if (pipt->pos > (strlen(pipt->text)-1)) {
struct Token token = {3, '\\0'};//3表示EOF,2表示+,1表示数字
return token;
}
char current_char = pipt->text[pipt->pos];
if (current_char>='0' && current_char<='9') {
struct Token token = {1, current_char};
pipt->pos++;
return token;
}
if (current_char == '+') {
struct Token token = { 2, current_char };
pipt->pos++;
return token;
}
printf("输入非法!\\n");
exit(-1);//如果都不是以上的字符,则报错并退出程序
}
char eat(struct Token* pcurrent_token, struct Interpreter* pipt, int type) {
char former_token_value = pcurrent_token->value;
if (pcurrent_token->type == type) {
*pcurrent_token = pipt->get_next_token(pipt);
}
else {
printf("输入非法!\\n");
exit(-1);
}
return former_token_value;
}
int expr(char* text) {
struct Interpreter ipt = {text, 0, get_next_token};
struct Token current_token = ipt.get_next_token(&ipt);
char temp;
temp = eat(¤t_token, &ipt, 1);//断言第一个字符是数字
int left = temp - '0';
eat(¤t_token, &ipt, 2);//断言第三个字符是加号
temp = eat(¤t_token, &ipt, 1);//断言第三个字符是数字
int right = temp - '0';
int result = left + right;
return result;
}
int main() {
char text[10];
while (1)
{
printf("请输入算式:\\n");
scanf_s("%s", text, sizeof(text));
int result = expr(text);
printf("= %d\\n\\n", result);
}
return 0;
}
运行结果:
请输入算式:
2+8
= 10
请输入算式:
1+5
= 6
请输入算式:
3+4
= 7
请输入算式:
以上是关于C/C++文档阅读笔记-A Simple Makefile Tutorial解析的主要内容,如果未能解决你的问题,请参考以下文章
Qt文档阅读笔记-Simple Anchor Layout Example解析
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 1.)(python/c/c++版)(笔记)
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 3.)(python/c/c++版)(笔记)
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 4.)(python/c/c++版)(笔记)
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 6.)(python/c/c++版)(笔记)
编译原理让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)