请教一下makefile依赖关系中的.h头文件

时间:2021-10-25 12:50:03
(1)在makefile的依赖关系中用不用体现.h头文件?
(2)如果在依赖关系中要体现.h头文件,应该体现到什么层次?
==============================
(1)在makefile的依赖关系中用不用体现.h头文件?
==============================
 下面是我的一些认识:
 头文件中定义的是接口(函数接口,文件外全局变量和宏定义),它的作用是向调用文件封装函数的实现过程。在第一次make的时候依赖关系中没有.h文件是没有关系的。所以主要讨论在修改文件的时候重新make的情况。
(一)接口
以一个类CBase的.h文件和.cpp文件为例:
//CBase.h
Class CBase
{
    ....
};
//CBase.cpp
CBasee::CBase(){}
....
//file upper.cpp
#include “CBase.h”
...
按照书上的例子这样的文件依赖关系应该为:
    upper.o : upper.cpp CBase.h
        gcc ........
    CBase.o : CBase.cpp CBase.h
        gcc ........
(1)如果函数实现(CBase.cpp)被修改而函数接口(CBase.h)没有被修改。这种情况应该是出现最多的情况,我们需要对函数的实现进行修改。这个时候很明显CBase.cpp将会有一个新的时间戳。所以在依赖关系中有没有CBase.h都可以。
(2)函数接口被修改。也就是.h文件被修改。在所有正常情况下对接口(.h)的修改必将导致相应的实现(.cpp)的修改和调用文件(upper.cpp)的修改(如果调用文件中使用了.h中的接口,淡然没使用upper.o就更不需要重新编译)。这样CBase.cpp和upper.cpp都有一个新的时间戳,所以在依赖关系中更不需要.h头文件。
(二)文件外全局变量。在头文件中的全局变量的申明是通知使用者在连接的时候要到文件中去找定义。所以在头文件中修改全局变量没有任何意义。一种情况是
//a.cpp
int a =5;
...
//a.h
extern int a;
...
//b.cpp
...
a++;
...
之前的make是ok的。如果我们人为的将a.h的全局变量的声明改为
extern int amm ;
这样make以后是不会报错的,除非在依赖关系中体现.h头文件。
(三)宏定义
我想这个是要使用.h头文件的最重要因素了。如果我们在头文件中定义
#define PI 3.14
然后我们想提升pi的精度,修改头文件
#define PI 3.1415
这样我们必须将头文件包含在makefile的依赖关系中。
===================================
(2)如果在依赖关系中要体现.h头文件,应该体现到什么层次?
===================================
一个例子
在一个Has_a的类关系中,
class CWhole
{
private:
    CPart1 Part1;
    CPart2 Part2;
    ...
}
这样我们在CWhole类的头文件中显然要include
#include "CPart1.h"
#include "CPart2.h"
....
而在CWhole类的实现文件中只要include
#include "CWhole.h"
那么我们在CWhole.cpp的依赖关系中要怎么写?
(1)CWhole.o :CWhole.cpp CWhole.h
         gcc ...
 (2) CWhole.o : CWhole.cpp CWhole.h CPart1.h CPart2.h ...CPartn.h
         gcc ...
 (3) ?????万一CPart1.h中#include的头文件要不要写在CWhole.o的依赖关系中?下面所有的头文件要不要写在依赖关系中

头大,请大家帮我想想

14 个解决方案

#1


sf

#2


头文件的处理一般是通过GCC命令和一些SHELL命令来完成的。
买本《GNU Make项目管理》来看吧,书里面有说。

#3


《GCC 技术参考》

#4


知识用时方很少!!

#5


那么我们在CWhole.cpp的依赖关系中要怎么写?
(1)CWhole.o :CWhole.cpp CWhole.h
         gcc ...
 (2) CWhole.o : CWhole.cpp CWhole.h CPart1.h CPart2.h ...CPartn.h
         gcc ...
 (3) ?????万一CPart1.h中#include的头文件要不要写在CWhole.o的依赖关系中?下面所有的头文件要不要写在依赖关系中
***********************
我个人的见解:头文件修改后,头文件的时间戳更新了。在源文件中的头文件include是发生在预处理时期,早于编译。所以我认为源文件的时间戳在编译前就改变了。故CWhole.h,CPart1.h,CPart2.h,... CPartn.h都不用写,只写cpp文件就行。 
楼主也可以用$(CC) -MM $(CFLAGS) sourcefile.cpp试试,看看gcc生成的依赖条件有没有包括那些头文件。

#6


在 C++ 中因为头文件包含了一些实现的内容(比如类成员的定义),而这些内容由于面向对象的封装原则,客户代码是不去理会的,然而编译客户代码时却是需要的。那么当这些内容发生改变的时候,实现文件(.cpp)往往也发生改变,这导致相应 .o 的重新编译生成。然而如果不把该头文件加入到客户代码的依赖关系中,因为客户代码没有改变,时间戳未发生变化,客户代码不会被重编译,而这种情况在连接时也未必会被发现,最后只能导致难以调试的运行时错误。

例如:

class SomeLinkList {
public:
    ...
    bool HasDupElement(); // 是否存在重复的元素
    ...
private:
    Node * pHead;
    Node * pTail;
};

HasDupElement() 这个方法原先以每次遍历链表的方式实现,后来程序员想到在每次插入新元素的时候查找一下用一个成员变量保存结果,每次返回这个变量值就可以了,效率更高。于是他修改了实现,并在类定义中添加了一个 bool 型的私有成员。这个改动对客户代码没有影响,客户代码不需要修改,这正是面向对象希望的。然而 C++ 的封装并不完全,在生成机器代码时客户代码依赖于使用的类的大小,因此虽然没有修改客户代码,但客户代码仍需要被重新编译。这时客户代码的时间戳不能解决问题,必须依赖于这个头文件的时间戳。

因此我认为不管何时,把用到的可能修改的头文件加入到依赖关系中可以省去很多麻烦,而这也恰恰反应了实际情况,也让自己对代码的依赖关系更加清晰,也让自己在代码中包含头文件时更加谨慎(仅包含需要的,这也会减少编译时间,减少包含关系错综复杂带来的混乱)。那种可以不写头文件依赖的技巧带来的结果是得不偿失的。

#7


<<跟我一起学MakeFile>>

#8


to channey():
   我对你的意思的理解试:如果头文件修改了,那么包含头文件的源文件的时间戳也会自动修改。这个不用试就知道试不正确的(因为编译过程是由依赖关系决定的,不是我预编译后来查看依赖关系)。就和我上面说的例子一样,如果我在头文件中定义一个#define PI 3在依赖关系中不包括.h文件时make。然后我只是修改头文件,将PI改为4,那么继续使用依赖关系中不包含改.h文件make,最后执行PI的值根本没有发生变化,还是3。

#9


顶nabie:你的讲解让我明白很多,thx very much!

#10


用 gcc -M 选项吧

#11


如果用GNU make,就会变得很简单了,可以参考下面的Makefile:
mymtom@fc6:src/csdn/make$ cat Makefile
PROG    = hello
SRCS    = main.c hello.c
DEPS    = $(SRCS:.c=.d)
OBJS    = $(SRCS:.c=.o)

RM      = rm -f

.SUFFIXES: .d
.c.d:
        $(CC) -MM $(CPPFLAGS) $< > $@

all:    $(PROG)

dep:    $(DEPS)

hello:  $(OBJS)
        $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@

-include $(DEPS)

clean:
        $(RM) $(OBJS) $(PROG) $(DEPS)

mymtom@fc6:src/csdn/make$ make
cc -MM  hello.c > hello.d
cc -MM  main.c > main.d
cc    -c -o main.o main.c
cc    -c -o hello.o hello.c
cc   main.o hello.o  -o hello
mymtom@fc6:src/csdn/make$ cat main.c
#include <stdio.h>

#include "hello.h"

int main(void)
{
        hello();
        return 0;
}

mymtom@fc6:src/csdn/make$ cat hello.c
#include <stdio.h>

#include "hello.h"

void hello(void)
{
        (void)printf("%s\n", HELLO);
}

mymtom@fc6:src/csdn/make$ cat hello.h
#define HELLO "Hello!"

void hello(void);

mymtom@fc6:src/csdn/make$

#12


学习中,看来路还长啊。

#13


学习中。。。要学的还真的真多呀。。

#14


依赖关系中要怎么体现.h头文件,这个问题我也想过,想不通了就认为是Makefile的隐含规则会自动处理的,但好多书上都明确的写出了头文件的依赖不能少,有时很矛盾啊.....

#1


sf

#2


头文件的处理一般是通过GCC命令和一些SHELL命令来完成的。
买本《GNU Make项目管理》来看吧,书里面有说。

#3


《GCC 技术参考》

#4


知识用时方很少!!

#5


那么我们在CWhole.cpp的依赖关系中要怎么写?
(1)CWhole.o :CWhole.cpp CWhole.h
         gcc ...
 (2) CWhole.o : CWhole.cpp CWhole.h CPart1.h CPart2.h ...CPartn.h
         gcc ...
 (3) ?????万一CPart1.h中#include的头文件要不要写在CWhole.o的依赖关系中?下面所有的头文件要不要写在依赖关系中
***********************
我个人的见解:头文件修改后,头文件的时间戳更新了。在源文件中的头文件include是发生在预处理时期,早于编译。所以我认为源文件的时间戳在编译前就改变了。故CWhole.h,CPart1.h,CPart2.h,... CPartn.h都不用写,只写cpp文件就行。 
楼主也可以用$(CC) -MM $(CFLAGS) sourcefile.cpp试试,看看gcc生成的依赖条件有没有包括那些头文件。

#6


在 C++ 中因为头文件包含了一些实现的内容(比如类成员的定义),而这些内容由于面向对象的封装原则,客户代码是不去理会的,然而编译客户代码时却是需要的。那么当这些内容发生改变的时候,实现文件(.cpp)往往也发生改变,这导致相应 .o 的重新编译生成。然而如果不把该头文件加入到客户代码的依赖关系中,因为客户代码没有改变,时间戳未发生变化,客户代码不会被重编译,而这种情况在连接时也未必会被发现,最后只能导致难以调试的运行时错误。

例如:

class SomeLinkList {
public:
    ...
    bool HasDupElement(); // 是否存在重复的元素
    ...
private:
    Node * pHead;
    Node * pTail;
};

HasDupElement() 这个方法原先以每次遍历链表的方式实现,后来程序员想到在每次插入新元素的时候查找一下用一个成员变量保存结果,每次返回这个变量值就可以了,效率更高。于是他修改了实现,并在类定义中添加了一个 bool 型的私有成员。这个改动对客户代码没有影响,客户代码不需要修改,这正是面向对象希望的。然而 C++ 的封装并不完全,在生成机器代码时客户代码依赖于使用的类的大小,因此虽然没有修改客户代码,但客户代码仍需要被重新编译。这时客户代码的时间戳不能解决问题,必须依赖于这个头文件的时间戳。

因此我认为不管何时,把用到的可能修改的头文件加入到依赖关系中可以省去很多麻烦,而这也恰恰反应了实际情况,也让自己对代码的依赖关系更加清晰,也让自己在代码中包含头文件时更加谨慎(仅包含需要的,这也会减少编译时间,减少包含关系错综复杂带来的混乱)。那种可以不写头文件依赖的技巧带来的结果是得不偿失的。

#7


<<跟我一起学MakeFile>>

#8


to channey():
   我对你的意思的理解试:如果头文件修改了,那么包含头文件的源文件的时间戳也会自动修改。这个不用试就知道试不正确的(因为编译过程是由依赖关系决定的,不是我预编译后来查看依赖关系)。就和我上面说的例子一样,如果我在头文件中定义一个#define PI 3在依赖关系中不包括.h文件时make。然后我只是修改头文件,将PI改为4,那么继续使用依赖关系中不包含改.h文件make,最后执行PI的值根本没有发生变化,还是3。

#9


顶nabie:你的讲解让我明白很多,thx very much!

#10


用 gcc -M 选项吧

#11


如果用GNU make,就会变得很简单了,可以参考下面的Makefile:
mymtom@fc6:src/csdn/make$ cat Makefile
PROG    = hello
SRCS    = main.c hello.c
DEPS    = $(SRCS:.c=.d)
OBJS    = $(SRCS:.c=.o)

RM      = rm -f

.SUFFIXES: .d
.c.d:
        $(CC) -MM $(CPPFLAGS) $< > $@

all:    $(PROG)

dep:    $(DEPS)

hello:  $(OBJS)
        $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@

-include $(DEPS)

clean:
        $(RM) $(OBJS) $(PROG) $(DEPS)

mymtom@fc6:src/csdn/make$ make
cc -MM  hello.c > hello.d
cc -MM  main.c > main.d
cc    -c -o main.o main.c
cc    -c -o hello.o hello.c
cc   main.o hello.o  -o hello
mymtom@fc6:src/csdn/make$ cat main.c
#include <stdio.h>

#include "hello.h"

int main(void)
{
        hello();
        return 0;
}

mymtom@fc6:src/csdn/make$ cat hello.c
#include <stdio.h>

#include "hello.h"

void hello(void)
{
        (void)printf("%s\n", HELLO);
}

mymtom@fc6:src/csdn/make$ cat hello.h
#define HELLO "Hello!"

void hello(void);

mymtom@fc6:src/csdn/make$

#12


学习中,看来路还长啊。

#13


学习中。。。要学的还真的真多呀。。

#14


依赖关系中要怎么体现.h头文件,这个问题我也想过,想不通了就认为是Makefile的隐含规则会自动处理的,但好多书上都明确的写出了头文件的依赖不能少,有时很矛盾啊.....