GNU Make Guide

zhaozj2021-02-16  83

GNU Make Guide ----------------------------------------------- --------------------------------- Translation: Ha 30000-10-20 16:22:19 Translator : This article is an article introducing GNU Make. After reading, readers should basically master the use of Make. Make is a tool that you want to program on UNIX (of course also include Linux) systems. If you don't use Make in the program you write, you will explain that the program you write is just a personal exercise program, does not have any practical value. Maybe this is a bit biased, but make it really uses in any slightly scale program. I hope this article can provide a good useful information for China's UNIX programming beginners. In addition to installing red hats, China Linux users should try to write some useful procedures. Personal idea, everyone reference. -------------------------------------------------- ------------------------------ C-Scene title # 2 multi-file project and GNU Make tool author: Qiaozhi Fu special (Goerge FOOT) Email: George.foot@meton.ox.ac.uk Occupation: Student At Merton Colleged, Oxford University, England Occupation: Student, Merton College, Oxford City University, England IRC Anonymous: Gfoot ----- -------------------------------------------------- ------------------------- Refuse to commit: Author for any damage caused by anything (the actual thing you have or not Abstract, or virtual). All damage is your own responsibility, but nothing to do with me. Ownership: The "Multi-File Project" part belongs to the author's property, copyright belongs to Georget Fug, from May to July, 1997. Other part belongs to CScene property, copyright CScene in 1997, all copyrights. The distribution, part or all of this CScene article shall be handled in accordance with all other CSCENE articles. 0) Introduction ~~~~~~~~~~~~~~ This article will first introduce why you want to separate your C source code into several reasonable independent files, when needed, how can it be better. Then tell you how GNU Make automates your compilation and connection steps. For users of other Make tools, although appropriate adjustments are made when using other similar tools, the content of this article is still very useful. If you have doubts about your own programming tool, you can try it actually, but please read the user manual first. 1) Multi-file item ~~~~~~~~~~~~~~~~~~~~ What do you use? First, there is a multi-file project? They seem to make things more complicated. Also wants the header file, but also the extern declaration, and if you need to find a file, you have to search in more files. But in fact, we have a strong reason to support us to break up a project into small pieces. When you change a line of code, the compiler needs to be re-compiled to generate a new executable. But if your project is separated in a few small files, when you change one of the files, the object file of the other source files already exists, so there is no reason to recompile them.

What you need to do is just that reproduces the file that is changed, and then reconnects all the target files. In a large project, this means that it is a simple adjustment from a long (minutes from a few hours). As long as the basic planning, a project is broken down into multiple small files to make it easier to find a code. Very simple, you decompose your code into a different file according to the role of the code. When you want to see a code, you can know accurately in that file to look for it. Generating a package from a lot of target files (library) is much better than from a single big target file. Of course, this is actually a advantage is determined by the system you use. However, when a package is connected to a program using GCC / LD (a GNU C Compilation / Connector), in the process of the connection, it will try to not use the part that is not used. But each time you can only remove a complete target file from the package. So if you refer to any of the symbols in a single target file, then this target file will be connected in. If a package is very fully decomposed, then after the connection, the obtained executable is much smaller than the files connected to the package consisting of a large target file. Because your program is very modular, the sharing part between files is minimized, then there are many benefits - it can be easily tracked into bugs, which often can be used in other projects, At the same time, others can easily understand what your code is doing. Of course, there are many other benefits ... 1.2 When you decompose your project, it is unreasonable to decompose anything is unreasonable. Simple programs like "World, you are good" are not divided, because there is nothing to be able to divide. Decompose the decomposition of small procedures for testing is also meaningless. But in general, when the decomposition project helps layout, development and readability, I will take it. In most cases, this is applicable. (The so-called "world, you are good", both 'Hello World', just a sample program that introduces a programming language, which will display a line 'Hello World' on the screen.) If you need Develop a considerable project, before the start, consider how you will implement it and generate a few files (with the appropriate name) to put your code. Of course, in the process of your project development, you can build new files, but if you do this, you may change the original idea, you should think about whether it needs to be adjusted for the overall structure. For medium-sized projects, you can certainly use the above skills, but you can also start entering your code, when your code is more difficult to manage, decompose them into different files. But in my experience, I will form a probably scheme in my mind, and try to follow it, or during development, as needed, it will make development easier. 1.3 How to Decompose the project first instructions, this is totally my personal opinion, you can (maybe you really will?) Do something else. This will touch the problem of coding style, and everyone has never stopped the debate on this issue. Here I just give me my favorite approach (while giving this reason): i) Do not point to multiple source files (exception: the header file of the package). The way to define a source code file is more efficient and easier to find. Otherwise, changing the structure of a source file (and its header file) must recompile several files.

II) If you can, you can use more than one header file to point to the same source code file. Sometimes the function prototype, type definition, and so on, which cannot be invoked, and it is very useful from their C source file files. Using a Header file public symbol, use another private symbol that if you change the internal structure of this source file, you can just recompile it without recompiling other sources that use its public header file. File. III) Do not repeat the definition information in multiple header files. If necessary, in one of the header files #include one, but do not repeat the same Header information twice. The reason is if you change this information later, you only need to change it once, do not search and change another repetition. IV) In each source file, #include states all Header files that declare the symbols in the source file file. In this way, the contradictions you make in some functions in the source file and the Header file can be discovered by the compiler. 1.4 Reviews for common errors a) Conversation (Identifier) ​​In the source file: In C, the default state of variables and functions is public. Therefore, any C source file file can reference universal (GLOBAL) functions and general variables existing in other source code archives, making this file without the declaration or prototype of the variable or function. So you must guarantee that you can't use the same symbol name in different two files, otherwise there will be an incorrect connection or a warning when compiling. A method of avoiding such an error is to add a prefix with its source file before the public symbol. For example: All functions in GFX.c are plus prefix "GFX_". If you are very careful to decompose your program, use meaningful function names, and not excessive common variables, of course, this is not a problem. To prevent a symbol from being seen outside of its defined source file, you can add keyword "static" before it definition. This is useful to use only simple functions that other files use only in one file. B) Multi-defined symbols: The Header file will be replaced with the location of the #include in your source file. Therefore, if the Header file is included in #include to more than one source file, all definitions in this header file will appear in each related source code file. This will cause the symbols in which they are defined over, thus connecting the connection error (see). Workaround: Do not define variables in the HEADER file. You only need to declare them in the Header file and then define them in the appropriate C source file (should be included in the Header file). For beginners, definitions and statements are easily confused. The role of the declaration is to tell the compiler that the symbols they have declared should exist and have the type specified. However, it does not make the compiler allocates storage space. The definition is required to require a compiler to allocate storage space. When making a statement instead of being defined, put a keyword "extern" before the declaration. For example, we have a variable called "counter". If we want it to become public, we define it in a source code (in one inside): "int counter;", then in the relevant header file declaration It: "Extern Int Counter;". The function prototype is implied with Extern, so you don't need to worry about this problem.

c) Repeat definition, repeat declaration, contradictory type: Please consider if there are two files in the #include in a C source file, and the AH is also #include BH file (the reason is that the BH file defines some ah needs type) What happens? At this time, the C source code file #include has b.h twice. So each of the #define in B.H has occurred twice, and each declaration has occurred twice, and so on. In theory, because they are exactly the same copy, there should be no problems, but in practical applications, this is not conforming to C's grammar, which may occur when compiling, or at least warning. The solution is to determine that every Header file is only included in any source file file. We generally use preprocessors to achieve this purpose. When we enter every Header file, we have a giant command for this Header file #define. We really use the body file of the Header file only if this giant intention is not defined. In practical applications, we only need to put the following of the following code on each Header file: #ifndef filename_h #define filename_h then put the following line size in the final: #ENDIF with the Header file file instead of FileName_h, use the bottom line instead of the point in the file. Some people like to make comments in #ENDIF to remind them that this #ndif refers to what. For example: #ENDIF / * #ifndef filename_h * / I personally there is no such habit, because this is actually very obvious. Of course, this is only different in the style of each person, and there is no harm. You only need to add this skill in the header file with compilation errors, but there is no loss in all HEADER files, which is a good habit. 1.5 Re-compiling a multi-file project clear distinct compilation and connection is important. The compiler uses the source code file to generate some form of destination file (Object Files). During this process, the external symbol reference is not explained or replaced. Then we use the connector to connect these object files and some standard package plus your specified package, and finally connect to generate an executable. At this stage, the reference to the symbols in other files is interpreted in a target file, and the reference that cannot be interpreted is generally reported in the form of error information. Basic steps should be, put your source file into a form of a target file, and finally connect all the target files to an executable file. How to do it is determined by your compiler. Here I only give the relevant commands of the GCC (GNU C compiler), which may also apply to your non-GCC compiler. GCC is a multi-objective tool. It calls other components (pre-processing programs, compilers, combination programs, connector) when needed. Which components of specific components are called depends on the type of input file and the switch you pass to it. In general, if you only give it C source file file, it will preprocess, compile, combine all files, and then connect the resulting target file into an executable file (generally generated file is named A.out). Of course you can do this, but this will destroy a lot of benefits we have decomposed into multiple files. If you give it a -C switch, GCC only compiles its file into a target file, named the file name of the source file but the suffix is ​​".c" or ".cc" becomes ".o".

If you give it a list of target files, GCC will connect them into executables, the default file name is A.out. You can change the default name, with the file name you specified by the switch-O. Therefore, after you change a source code file, you need to recompile it: 'gcc -c filename.c' then reconnect your item: 'gcc -o exec_filename * .o'. If you change a Header file, you need to recompile all #include's source files for this file, you can use 'gcc -c file1.c file2.c file3.c' and then connect it as above. Of course, this is very cumbersome, fortunately, we have some tools to make this step simple. The second part of this article is to introduce one of the tools: GNU Make tool. (Good guy, now I started to see the true chapter. You have learned something?) 2) GNU Make Tool ~~~~~~~~~~~~~~~~ 2. The basic Makefile structure Gnu make's main job is Read into a text file, makefile. This file is mainly related to which files ('target' destination file) is from which other files ('DependenCIES' relying on files), what commands are used for this process. With this information, Make will check the file on the disk, if the timestamp of the destination file (the time when the file is generated or changed) is more than the older file, make, make the corresponding command so that make Update the destination file. (The destination file is not necessarily the final executable, which can be any file.) Makefile is typically called "makefile" or "makefile". Of course, you can specify other file names at the Make command line. If you don't specify, it will find "makefile" or "makefile", so use these two names is the simplest. A Makefile mainly contains a range of rules, as follows: ... (tab)................................. o bar.o -o myprog foo.o: foo.c foo.h bar.h gcc -c foo.c -o foo.o bar.o: bar.c bar.h gcc -c bar.c-c bar .o === makefile end === This is a very basic makefile - make from the top, first destination, 'myprog', as its main goal (one it needs to guarantee Always the latest ultimate goal). The rules given instructions As long as the file 'myprog' is more old than any of the file 'foo.o' or 'bar.o', the next line will be executed. However, before checking the timestamp of the file foo.o and bar.o, it will look down the rules that make foo.o or bar.o as the target file. It found about foo.o rules that rely on files foo.c, foo.h and bar.h. It can't find a rule that generates these relying on files from below, it starts to check the timestamp of these dependencies on the disk.

If any of the timestamps in these files are more than foo.o, the command 'gcc -o foo.o foo.c' will be executed, so that the file foo.o is updated. Next, a similar inspection is made to the file bar., and the file is here is the file bar.c and bar.h. Now, make back to 'myprog' rules. If any one of the two rules is executed, myProg needs to be rebuilt (because one of them. "New than 'myprog', the connection command will be executed. I hope that you can see the benefits of using the Make tool to create a program - all cumbersome inspection steps in the previous chapter are made by Make: Check the timestamp. A simple change in your source file will cause that file being recompiled (because the .o file rely on .c file), the executable file is reconnected (because .o file is changed). In fact, the real benefits are when you change a Header file - you no longer need to remember that source file rely on it, because all the information is in makefile. Make will easily recompile all the source code files that have changed since this Header file, if necessary, re-connect. Of course, you have to make sure that the rules you write in makefile are correct, only listing the Header files of #include in the source file ... 2.2 Writing the Make rule (Rules) is most obvious (also the simplest) The method of writing rules is a check-in source file that makes their target file as a purpose, while the C source file and the Header file of #include are relying on files. But you also have to list other Header files of these Header files #include as relying on files, as well as files included in the files included ... Then you will find more and more files, then Your hair begins to fall off, your temper begins to get bad, your face turns into a dish, you start on the road to collide with the electric pole, and finally you smash your computer monitor, stop programming. Is there any way to have some way? Of course! To the compiler! When compiling each source file, it should be found to include what kinder files should be included. When using GCC, use the -m switch, which will output a rule for each of your C file, make the target file as a purpose, and this C file and all Header files that should be #include will be used as relying file. Note that this rule will join all Header files, including the files surrounded by cornence brackets (`<',`>') and double quotes (`"). In fact, we can quite certainly determine the system's Header file (such as stdio.h, STDLIB.H, etc.) Will not be changed by us, if you use -mm instead of transfer to GCC, those HEADER files surrounded by cornence numbers will not be included. (This saves some compile time) by GCC The output of the output does not contain a command part; you can write your command or nothing, let Make use its implicit rules (refer to the following 2.4). These Makefiles Contains some rules. The other East West is the variable definition. The variables in the makefile are like an environment variable (Environment Variable).

In fact, environmental variables are interpreted as MAKE variables during Make. These variables are sensitive, generally using uppercase letters. They can be referenced from almost anywhere, or they can be used to do a lot of things, such as: i) Store a list of file names. In the above example, the rules that generate executables include some destination file names. The same files in the command line of this rule are delivered to the GCC as a command parameter. If you use a variable in this to store all the target file names, add a new target file to make a simple and less error-free. II) Store executable file name. If your project is used in a non-GCC system, or if you want to use a different compiler, you must change the place to use the compiler into a new compiler name. But if you use a variable instead of the compiler name, then you only need to change a place, and the command name of all its places has changed. Iii) Storage The compiler flag. Suppose you want to pass a set of the same options (for example, -wall -o -g) if you put this group of options, then you can put this variable in all call compilers. . And when you want to change the option, you only need to change this variable in one place. To set a variable, you only need to write the name of this variable on a line, follow the back to one = number, followed by the value of this variable you want to set. In the future, you have to reference this variable, write a $ symbol, followed by the variable name surrounded in parentheses. For example, in the following, we use the previous makefile to rewrite the variables again: === Makefile start === Objs = foo.o bar.o cc = GCC cflags = -wall -o -g myprog: $ (OBJS) $ ( CC) $ (objs) -o myprog foo.o: foo.c foo.h bar.h $ (cc) $ (cflags) -c foo.c -o foo.o bar.o: bar.c bar.h $ (Cc) $ (cflags) -c bar.c -o bar.o === makefile ends === There are also some set internal variables, which are defined according to each rule content. Three more useful variables are $ @, $

('man make', 'man makefile') 2.4 Implicit Rules Note that in the above example, several commands of the .o file are the same. They are all generated from .o files and related files, which is a standard step. In fact, Make already knows how to do - it has some rules called implicit rules, which tells it when you don't give some commands, what should I do. If you delete the commands that generate foo.o and bar.o from their rules, Make will find its implicit rules and then find an appropriate command. Its command uses some variables, so you can set it according to your ideas: it uses the variable CC as a compiler (like our example), and transmits variable cflags (for C compiler, C compiler) CXXFLAGS, CPPFLAGS (C pre-processor flag), target_arch (now don't consider this now), then it is added to the flag '-C', back with the variable $ <(first rely) -o 'with variable $ @ (destination file name). A concrete command of a CC will be: $ (cc) $ (cflags) $ (CPPFLAGS) $ (target_arch) -c $ <-o $ @ Of course you can define these variables in your own needs. This is why the code output with the -m or -mm switch with GCC can be used directly in a Makefile. 2.5 PHONY TARGETS, assuming that one of your projects will eventually generate two executable files. Your main goal is to generate two executable files, but these two files are independent of each other - if a file needs to be rebuilt, does not affect the other. You can use the "Imaginary Purpose" to achieve this effect. A illusion is almost the same as a normal purpose, but this destination file does not exist. Therefore, Make always assume that it needs to be generated. When it is updated, it will execute the command line in its rule. If you start at our makefile: All: Exec1 exec2 where Exec1 and Exec2 are two executables we are as a purpose. Make put this 'all' as its main purpose, try to update 'all' every time you execute. But since this line is in this line rule, there is no actual file called 'all' (in fact ALL does not actually be generated on the disk), so this rule does not really change the status of 'all'. Since this file does not exist, Make will try to update the all rule, so check it to rely on Exec1, if EXEC2 needs to be updated, if necessary, reach our purpose. Illustrative purposes can also be used to describe a set of non-preset actions. For example, you want to delete all files generated by makefile, you can set up such rules in Makefile: VeryProg Prerequisite is that there is no other rule depending on this 'Veryclean' purpose, it will never be carried out.

However, if your clear use of the command 'make verycleclean', Make will use this purpose as its primary goal, perform those RM commands. What happens if there is a virtual file on your disk? This is because there is no dependencies in this rule, so this destination file must be the latest. (All relying on files are already the latest), so both make the user confirm the command Make to re-generate it, no Anything happened. The solution is to mark all the ingredients (with .phony), telling make not to check if they exist on the disk, nor to find any implicit rules, directly assume that the specified purpose needs to be updated. Add the following line to the Makefile to include the rules of the above rules: .phony: VeryClean is OK. Note that this is a special Make rule, make know .phony is a special purpose, of course, you can join any illusion you want to use in its relying on it, and Make knows that they are ingredients. 2.6 Functions Makefile The function is similar to its variables - when you use, you use a $ symbol with the bracket, the function name, and the space is followed by the comma-separated parameters, and finally use the brackets. For example, there is a function called 'Wildcard' in GNU Make, which has a parameter, which is to expand into a list of file names that are described by its parameters, and the file is spaced apart. You can use this command below: Sources = $ (Wildcard * .c) This line generates a list of all files ending with '.c', and then stores into the variable Sources. Of course, you don't need to store the results into a variable. Another useful function is the PATSUBST (Patten Substitude, the abbreviation of matching replacement) functions. It requires three parameters - the first is a model that needs to match, the second means to replace it, the third is a word that needs to be processed by the space separated by the space. For example, dealing with variables after the above definition, OBJS = $ (PatSubst% .c,%. O, $ (Sources)) This line will process all words in the Sources list (a column file name) if it ends Is '.c', use '.o' to replace '.c'. Note that the% symbols here will match one or more characters, and it is called a 'handle' each time the string is called. In the second parameter, the% is interpreted as the handle that matches the first parameter. 2.7 A more effective makefile takes us now, we can build a quite effective Makefile. This makefile can complete most of our needs, you can use it directly in most projects without doing too much change. First we need a basic Makefile to build our program. We can let it search for the current directory, find the source file, and assume that they belong to our item, put it into a variable called Sources. Here, if all * .cc files are also included, it may be more insurance because the source code file may be a C code. Sources = $ (Wildcard * .c * .cc) Using PATSUBST, we can generate the target file name by the source file name, we need to compile these target files.

If our source code file has both .c files, there are also .cc files, we need to use the embedded PATSUBST function call: OBJS = $ (PatSubst% .c,%. O, $ (PatSubst% .cc,%. O, $ (SOURCES))) The most inside of the PATSUBST called the .cc file suffixed alternative, the resulting result was replaced by the external PATSUBST call processing, which was replaced with the .c file suffix. Now we can set up a rule to build an executable: MyProg: $ (objs) gcc -o myprog $ (objs) further rules do not necessarily need, GCC already knows how to generate the target file (Object Files). Below we can set rules that have relying on information: Depends: $ (SOURCES) GCC -M $ (Sources)> Depends here If a file called 'Depends' does not exist, or any source file is longer than an existing Depends This file is new, then a Depends file will be generated. The Depends file will contain rules for the source code file generated by GCC (Note -M Switch). Now let's let Make make these rules as part of the Makefile file. The techniques used here are like the #include system in the C language - I ask Make to include this file INCLUDE to Makefile, as follows: Include Depends GNU Make See this, check whether the 'Depends' is updated, if not, it Use our command to re-generate the Depends file. It will then contain this set (new) rule into in and continue to handle the final target 'myprog'. When you see rules about MyProg, it checks if all target files are updated - using the rules in the Depends file, of course these rules are now updated. This system is actually a low efficiency, because whenever a source code file is changed, all source files must be preproced to generate a new 'Depends' file. And it is not 100% security, because when a Header file is changed, relying on information is not updated. But on the basic work, it is also quite useful. 2.8 A better makefile This is a Makefile I have designed for my most items. It should be able to do not need to modify it in most items. I mainly use it on DJGPP, which is a DOS version of the GCC compiler. So you can see the executed command name, 'alleg' package, and RM -F variables reflect this.

=== Makefile start === ####################### # # # gener makefile # By George Foot # email: George.foot@meton.ox.ac.uk # # Copyright (c) 1997 George Foot # all rights reserved. # 保 all copyright # # no warranty, no liability; # you us this at Your Own Risk. # 不 保, is not responsible # 你 你用 这个用 你 风 风 # # you are free to modify and # distribute this without giving # credit to the original author. # You can change and distribute this file # And no need to give the original author. # (How is your meaning?) # ################################################################################################################################################################################################################################################################################################ user setting # # Adjust the following if necessary; EXECUTABLE is the target # executable's filename, and LIBS is a list of libraries to link in # (eg alleg, stdcx, iostr, etc) You can override these on make's # command line. The following things are adjusted if needed, if needed. Executable is the target's executable file name, libs # is a list of packages that require a connection (such as Alleg, Stdcx, iostr, etc.). Of course, you can overwrite them on the command line of make, you are willing to have no problem. # Executable: = Mushroom.exe Libs: = alleg # now alter any implicit rules' variables if you like, eg: # # Now change the variables in the implicit rules you want to change, such as cflags: = -g -wall -O3 -m486 cxxflags: = $ (cflags) # The next bit checks to seehther rm is in your djgpp bin # Directory; if not it Uses del instead, but this can cause (harmless) # `File Not find 'Error Messages . If you are not usning dos at all, # set the variable to something # files. # # Let's check if there is any rm command in your DJGPP command directory, if not, we use the # del command instead, But it is possible to give us 'File Not Found' this error message, this is not # 什么 #. If you are not using DOS, set it into a command that deletes files and unused. # (In fact, this step in the Unix class is redundant, but it is convenient for DOS users. Unix # users can delete this 5 line command.

IFNEQ ($ (Wildcard $ (DJDIR) /BIN/rm.exe),) RM-F: = RM -F ELSE RM-F: = Del Endif # You Shouldn't Need To Change Anything Below this point. # # From here, you should don't need to change anything. (I am not trustful, too NB!) Source: = $ (Wildcard * .c) $ (Wildcard * .cc) Objs: = $ (PatSubst% .c,%. O, $ (PatSubst% .cc, % .o, $ (Source))) DEPS: = $ (PatSubst% .o,%. D, $ (OBJS)) missing_deps: = $ (Filter-Out $ (Wildcard $ (DEPS)), $ (DEPS) Missing_Deps_sources: = $ (Wildcard $ (PatSubst% .d,%. C, $ (Missing_DEPS)) / $ (PatSubst% .d,%. CC, $ (Missing_DEPS))) CPPFLAGS = -Md .phony: Everything DEPS OBJS CLEAN VERYCLEAN REBUILD EVERYTHING: $ (Executable) DEP: $ (DEPS) OBJS: $ (OBJS) Clean: @ $ (rm-f) * .o @ $ (rm-f) * .d verycleclean: clean @ $ (RM-F) $ (Executable) Rebuild: Veryclean Everything IFNEQ ($ (missing_deps),) $ (Missing_Deps): @ $ (r-) $ (PATSUBST% .D,%. O, $ @) endif -Include $ (DEPS) $ (Executable): $ (OBJS) GCC -O $ (Executable) $ (OBJS) $ (Addprefix -l, $ (LIBS)) === Makefile ends === There are several places worth explaining of. First, when I define most of the variables, I use: = instead of = symbol. Its role is to immediately expand the functions and variables referred to in the definition. If you use =, the function and variable reference will remain in that child, that is, the value of changing a variable can cause the value of other variables to be changed. For example: a = foo b = $ (a) # now B is $ (a), and $ (a) is 'foo'. A = BAR # now B is still $ (a), but its value has become 'bar'. B: = $ (a) # The value of B is 'bar'. The value of a = foo # b is still 'bar'. Make will ignore all the text behind the # symbol until the end of the line. Ifneg ... Else ... Endif System is a failure / effective tool that makes a certain part of the code in your Makefile. IFEQ uses two parameters if they are the same, it is not until Else (or ENDIF, if there is no else), adding Makefile; if different, add a code between ELSE to ENDIF to Makefile (if there is ELSE) . The use of IFNEQ is just the opposite.

转载请注明原文地址:https://www.9cbs.com/read-16448.html

New Post(0)