Macro definition details

Posted by abgoosht on Fri, 04 Mar 2022 18:54:18 +0100

Macro defined Dark Magic - macro rookie takeoff manual

Macro definition plays an important role in the development of C system. Needless to say, in order to compile, optimize and facilitate, as well as cross platform capabilities, macros are widely used. It can be said that it will be difficult for the underlying development to leave define. When developing at a higher level, we will focus more on business logic, and it seems that we don't use and rely much on macros. However, the benefits of using macro definition are self-evident. While saving workload, code readability is greatly increased. If you want to be a developer who can write beautiful and elegant code, macro definition is definitely an essential skill (although macros themselves may not be beautiful and elegant XD). However, for many people, macro definition is not something they touch every day like business logic. Even if some macros can be used occasionally, they only stay at the level of use, but do not explore what is happening behind them. Some developers do have the motivation and willingness to explore, but after clicking on a definition, they find that there are countless other definitions in the macro definition. In addition, the full screen is different from the usual code, which can not understand and change color, so they get annoyed and retreat in anger. This paper hopes to express some basic rules and skills in the macro definition world of c-series language through several examples in a step-by-step way. Starting from 0, I hope you can at least understand and restore some relatively complex macros. Considering that I use more objc now, most readers of this site should also use objc, so some examples are selected from objc, but most of the content of this article will be common in c-series language.

introduction

If you don't know what a macro is, you can warm up first. When introducing macros, many people will say that macros are very simple, that is, simple search and replacement. Well, only half right. Macros in C are divided into two categories: object like macro and function like macro. It is relatively simple for object macros, but it is not so simple to find and replace. Object macros are generally used to define constants, for example:

    
  1. //This defines PI
  2. #define M_PI 3.14159265358979323846264338327950288

#The define keyword indicates that you are about to start defining A macro, followed by M_PI is the name of the macro, and the number after the space is the content. Macros like #define X and A are relatively simple. When compiling, the compiler will replace x with A after semantic analysis identifies it as A macro. This process is called macro expansion. For example, for m above_ PI

    
  1. #define M_PI 3.14159265358979323846264338327950288
  2. double r = 10 . 0 ;
  3. double circlePerimeter = 2 * M_PI * r ;
  4. // => double circlePerimeter = 2 * 3.14159265358979323846264338327950288 * r;
  5. printf ( "Pi is %0.7f" , M_PI );
  6. //Pi is 3.1415927

So let's start with another kind of macro. As the name suggests, a function macro is a macro that behaves like a function and can accept parameters. Specifically, when defining, if we follow a pair of parentheses after the macro name, the macro becomes a function macro. Start with the simplest example, such as the following function macro

    
  1. //A simple function-like macro
  2. #define SELF(x) x
  3. NSString * name = @ "Macro Rookie" ;
  4. NSLog ( @ "Hello %@" , SELF ( name ));
  5. // => NSLog(@"Hello %@",name);
  6. // => Hello Macro Rookie

What this macro does is that if SELF is encountered during compilation, and it is followed by parentheses, and the number of parameters in parentheses is consistent with the definition, then the parameters in parentheses will be changed to the defined content, and then the original content will be replaced. Specifically, in this code, SELF accepts a name, and then replaces the entire SELF(name) with name. Um It seems to be very simple and useless. You will think that this macro is written to sell cute. A macro that accepts multiple parameters must be fine, for example:

    
  1. #define PLUS(x,y) x + y
  2. printf ( "%d" , PLUS ( 3 , 2 ));
  3. // => printf("%d",3 + 2);
  4. // => 5

Compared with object macros, function macros are more complex, but do they look quite simple? Well, now that the warm-up is over, let's officially open the door to macro.

The world of macro has its own universe

Because macro expansion is actually the preprocessing of the editor, it can control the program source code itself and compilation process at a higher level. It is this feature that endows macro with powerful functions and flexibility. However, everything has two sides. Behind obtaining flexibility, it takes a lot of time to consider various boundary conditions. It may not be very understandable to say so, but most macros (especially function macros) have their own stories behind them. It will be interesting to explore these stories and design ideas. In addition, I always believe that learning in practice is the only way to really master knowledge. Although you may not be planning to write some macros by yourself at first, we might as well start learning and mining from actual writing and mistakes, because only muscle memory and brain memory work together, To reach the level of mastery. It can be said that the process of writing and using macros must be a process of learning and deep thinking in making mistakes. What we need to do next is to reproduce this series of processes to improve progress.

The first topic is, let's implement a MIN macro: implement a function macro, give two digital inputs, and replace it with the smaller number. For example, the value of MIN(1,2) is 1. HMM, simple enough? Define the macro, write the name, two inputs, and then replace it with a comparison value. Compare values. Any entry-level C program design will talk about it, so we can quickly write our first version:

    
  1. //Version 1.0
  2. #define MIN(A,B) A < B ? A : B

Try a minute

    
  1. int a = MIN ( 1 , 2 );
  2. // => int a = 1 < 2 ? 1 : 2;
  3. printf ( "%d" , a );
  4. // => 1

Correct output, package and publish!

However, in practical use, we soon encountered such a situation

    
  1. int a = 2 * MIN ( 3 , 4 );
  2. printf ( "%d" , a );
  3. // => 4

It may seem incredible, but we will expand the macro to know what happened

    
  1. int a = 2 * MIN ( 3 , 4 );
  2. // => int a = 2 * 3 < 4 ? 3 : 4;
  3. // => int a = 6 < 4 ? 3 : 4 ;
  4. // => int a = 4 ;

Well, when the bug comes out and the reason is known, everyone is Zhuge Liang afterwards. Because the priority of less than and comparison symbols is low, multiplication is calculated first. The correction is very simple. Just add parentheses.

    
  1. //Version 2.0
  2. #define MIN(A,B) (A < B ? A : B)

This time, the formula of 2 * MIN(3, 4) was won easily and happily. After this modification, we greatly increased our confidence in our macro... Until one day, an angry colleague came and threw the keyboard, and then gave an example:

    
  1. int a = MIN ( 3 , 4 < 5 ? 4 : 5 );
  2. printf ( "%d" , a );
  3. // => 4

Simply compare three numbers and find the smallest one. It's because you didn't provide a macro of three numbers. Poor colleagues had to compare 4 and 5 by themselves. When you start to solve this problem, your first thought may be whether it is OK to write MIN(3, MIN(4, 5)) since they are all seeking the minimum. So you change it casually and find that the result becomes 3, which is exactly what you want Next, I began to doubt whether I had read the wrong result before. I changed it back to the original, and a 4 suddenly appeared on the screen. You finally realize that things are not as simple as you think, so go back to the most primitive and direct means and expand the macro.

    
  1. int a = MIN ( 3 , 4 < 5 ? 4 : 5 );
  2. // => int a = (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5); // I hope you remember
  3. // => int a = ((3 < (4 < 5 ? 4 : 5 ) ? 3 : 4 ) < 5 ? 4 : 5 ); //In order to make you less tangled, I put parentheses on this formula
  4. // => int a = ((3 < 4 ? 3 : 4 ) < 5 ? 4 : 5 )
  5. // => int a = (3 < 5 ? 4 : 5 )
  6. // => int a = 4

The problem is found. Because the connection symbol and the operation symbol in the expanded formula have the same priority during expansion, the calculation order has changed. In essence, it is similar to the problem encountered in our version 1.0, or it is not well considered. Then be more strict, version 3.0!

    
  1. //Version 3.0
  2. #define MIN(A,B) ((A) < (B) ? (A) : (B))

As for why MIN(3, MIN(4, 5)) in version 2.0 has no problem and can be used correctly, here as an exercise, you can try to expand it yourself to see what happened.

After two tragedies, you are now full of doubts about this simple macro. So you ran countless test cases and they all passed. It seems that we have completely solved the bracket problem. You also think this macro will be appropriate from now on. But if you really think so, Tucson is broken. Life is always cruel, and the bug s that should come will come. Not surprisingly, on a cloudy afternoon, we received another example of a problem.

    
  1. float a = 1 . 0 f ;
  2. float b = MIN ( a ++ , 1 . 5 f );
  3. printf ( "a=%f, b=%f" , a , b );
  4. // => a=3.000000, b=2.000000

Your first reaction may be the same as me when you get this problem example. Who in TM is still doing + +, which is a mess! However, such people will exist and such things will happen. You can't say that others have wrong logic. Add 1 to a + +, and then calculate with 1. In fact, what this formula wants to calculate is to take the minimum value of a and b, and then a is equal to a plus 1: so the correct output a is 2 and b is 1! Well, my eyes are full of tears. Let's those long-standing programmers calmly expand this formula and see what happens this time:

    
  1. float a = 1 . 0 f ;
  2. float b = MIN ( a ++ , 1 . 5 f );
  3. // => float b = ((a++) < (1.5f) ? (a++) : (1.5f))

In fact, it's very clear as long as you start one step. When comparing a + + and 1.5f, take 1 and 1.5 first, and then a increases by 1. Next, after the condition comparison is true, a + + is triggered again. At this time, a is already 2, so b gets 2. Finally, a increases by itself again and the value is 3. The root cause of the error is that we expected a + + to be executed only once, but due to macro expansion, a + + was executed more, which changed the expected logic. Solving this problem is not a very simple thing, and the way of use is also very clever. We need to use a GNU C assignment extension, that is ({...}) Form of. This form of statement can be similar to many scripting languages. After sequential execution, the last assignment of the expression will be returned. For a simple example, after the following code is executed, the value of a is 3, and b and c only exist in the code field limited by braces

    
  1. int a = ({
  2. int b = 1 ;
  3. int c = 2 ;
  4. b + c ;
  5. });
  6. // => a is 3

With this extension, we can do many things we couldn't do before. For example, completely solve the problem of MIN macro definition, which is the standard writing method of MIN in GNU C

    
  1. //GNUC MIN
  2. #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })

Three statements are defined here, which are respectively declared with the type of input__ A and__ b. And use the input to assign a value to it. Next, make a simple condition comparison and get__ A and__ B and returns the result as a result using the assignment extension. This implementation ensures that the original logic is not changed, and the first assignment is performed, which also avoids the problem of bracket priority. It can be said to be a better solution. If the compiling environment supports this extension of GNU C, there is no doubt that we should write our MIN macro in this way. If we do not support this environment extension, we can only artificially specify parameters without operation or function call to avoid errors.

We have talked about MIN enough, but there is still a suspense. If there are already in the same scope__ A or__ If the definition of b (although generally speaking, there will be no such tragic naming, but who knows), this macro may have problems. After declaration, the assignment cannot be initialized due to repeated definitions, resulting in unpredictable macro behavior. If you are interested, you might as well try it yourself and see what the result will be. Apple has completely solved this problem in clang. We open Xcode to build a new project, enter MIN(1,1) in the code, and then click Cmd + to find the writing method of MIN in clang. For the convenience of explanation, I directly transcribe the relevant parts as follows:

    
  1. //CLANG MIN
  2. #define __NSX_PASTE__(A,B) A##B
  3. #define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
  4. #define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); })

It seems a little long and hard to work. Let's beautify this macro first and the last one__ NSMIN_IMPL__ The content is too long. We know that line breaks can be inserted into code without affecting the meaning. Can macros also be inserted? The answer is yes, but we can't use a single carriage return to complete it. Instead, we must add a backslash before the carriage return \. Rewrite it and add a line break to make it look better:

    
  1. #define __NSX_PASTE__(A,B) A##B
  2. #define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
  3. #define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); \
  4. __typeof__( B) __NSX_PASTE__( __b,L) = ( B) ; \
  5. ( __NSX_PASTE__( __a,L) < __NSX_PASTE__( __b,L)) ? __NSX_PASTE__( __a,L) : __NSX_PASTE__( __b,L) ; \
  6. })

However, it can be seen that MIN is composed of three macro definitions. First__ NSX_PASTE__ The two consecutive pound signs ## in the macro are a special symbol, which indicates the operation of connecting two parameters. Note that the function macro must be a meaningful operation, so you can't directly write AB to connect the two parameters, but need to write A##B in the example. There are all other self-contained operation symbols in macros, which we will introduce later. Next is the MIN of the two parameters we call. What it does is call another macro with three parameters__ NSMIN_IMPL__, The first two parameters are our input, and the third parameter__ COUNTER__ We don't seem to know it or where it comes from. Actually__ COUNTER__ Is a predefined macro. This value will be counted from 0 in the compilation process, and will be increased by 1 every time it is called. Because of uniqueness, it is often used to construct independent variable names. With the above foundation, let's look at the final implementation macro, which is very simple. The overall idea is the same as the previous implementation of GNUC MIN, except that it is the variable name__ A and__ B adds a counting suffix, which greatly avoids the possibility of problems caused by the same variable name (of course, if you stubbornly call the variable _a9527 and something goes wrong, you can only say that you won't die if you don't die).

After a lot of effort, we finally figured out a simple MIN macro. Macro is such a kind of thing. There are many mysteries hidden under the simple surface. It can be said that there is little heaven and earth. As an exercise, you can try to implement a SQUARE(A) to input a number and output its square macro. Although this calculation is usually done with inline now, we can implement it well through the idea similar to MIN. let's have a try:)

Log, eternal theme

Log is loved by everyone. It shows us the way forward and helps us catch insects. In objc, the log method we use most is NSLog to output information to the console. However, the standard output of NSLog is disabled and there is not enough useful information. For example, the following code:

    
  1. NSArray * array = @[ @"Hello" , @"My" , @"Macro" ];
  2. NSLog ( @"The array is %@" , array );

The result printed to the console is similar to this

    
  1. 2014-01-20 11 :22 :11.835 TestProject [23061:70b] The array is (
  2. Hello,
  3. My,
  4. Macro
  5. )

What do we care about when we export? In addition to the results, in many cases, we will be more concerned about the file location and method of this line of log. It is undoubtedly a stupid way to manually add the method name and location information in each NSLog, and if there are many NSLog calls in a project, it is undoubtedly a nightmare to change them manually one by one. Through macros, we can easily improve the native behavior of NSLog, which is elegant and efficient. You just need to add

    
  1. //A better version of NSLog
  2. #define NSLog(format, ...) do { \
  3. fprintf(stderr, "<%s : %d> %s\n", \
  4. [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
  5. __LINE__, __func__); \
  6. (NSLog)((format), ##__VA_ARGS__); \
  7. fprintf(stderr, "-------\n"); \
  8. } while ( 0)

Well, this is the longest macro we've seen so far... It doesn't matter. Just analyze it bit by bit. The first is the definition part, the NSLog(format,...) in line 2. What we see is a function macro, but its parameters are strange. The second parameter is, In the macro definition (actually including function definition), it is written as The parameters of are called variable parameters. The number of variable parameters is not limited. In this macro definition, except that the first parameter format will be processed separately, the next input parameters will be treated as a whole. Recall the usage of NSLog. When using NSLog, we often give a format string as the first parameter, and then write the variables to be output in the following parameters according to the defined format. The first format string here corresponds to the format in the macro, and all the following variables are mapped to As a whole.

Next, the content part of the macro. We came across a do while statement... When was the last time you used do while? Maybe it's a big assignment for C programming class? Or at a long forgotten algorithm interview? In short, although everyone understands this sentence, there are few opportunities to use it in practice. At first glance, it seems that this do while does nothing. Because while is 0, do will be executed only once. So what is the meaning of its existence? Can we directly simplify this macro, remove it and become like this?

    
  1. //A wrong version of NSLog
  2. #define NSLog(format, ...) fprintf(stderr, "<%s : %d> %s\n", \
  3. [[[NSString stringWithUTF8String :__FILE__] lastPathComponent] UTF8String], \
  4. __LINE__, __func__) ; \
  5. ( NSLog)(( format), ##__VA_ARGS__) ; \
  6. fprintf( stderr, "-------\n") ;

Of course, the answer is No. you may not encounter problems in simple tests, but this macro is obviously tragic in the production environment. Consider the following common situations

    
  1. if ( errorHappend )
  2. NSLog ( @"Oops, error happened" );

When expanded, it will become

    
  1. if ( errorHappend )
  2. fprintf (( stderr , "<%s : %d> %s \n " ,[[[ NSString stringWithUTF8String : __FILE__ ] lastPathComponent ] UTF8String ], __LINE__ , __func__ );
  3. ( NSLog )(( format ), ## __VA_ARGS__ ); //I will expand this later
  4. fprintf ( stderr , "------- \n " );

be careful.. C-series languages do not control code blocks and logical relationships by shrinking in. So if the person using this macro doesn't enlarge the parentheses after the condition judgment, your macro will always call the real NSLog output, which is obviously not the logic we want. Of course, we still need to re criticize the students who think that there is no problem that the single execution statement after if does not have parentheses. This is a bad habit and there is no reason. Please correct it. Whether it is a statement or not, whether it is after if or else, braces are added, which is a kind of respect for others and yourself.

Well, if you know how our macro fails, you will know the modification method. As a macro developer, we should try our best to ensure that users will not make mistakes under the maximum conditions. Therefore, we think of directly enclosing the macro content with a pair of curly braces. Maybe everything will be fine? like this:

    
  1. //Another wrong version of NSLog
  2. #define NSLog(format, ...) {
  3. fprintf ( stderr , "<%s : %d> %s \n " , \
  4. [[[ NSString stringWithUTF8String : __FILE__ ] lastPathComponent ] UTF8String ], \
  5. __LINE__ , __func__ ); \
  6. ( NSLog )(( format ), ## __VA_ARGS__ ); \
  7. fprintf ( stderr , "------- \n " ); \
  8. }

Expand the formula just now, and the result is

    
  1. //I am sorry if you don't like { in the same like. But I am a fan of this style :P
  2. if ( errorHappend ) {
  3. fprintf (( stderr , "<%s : %d> %s \n " ,[[[ NSString stringWithUTF8String : __FILE__ ] lastPathComponent ] UTF8String ], __LINE__ , __func__ );
  4. ( NSLog )(( format ), ## __VA_ARGS__ );
  5. fprintf ( stderr , "------- \n " );
  6. };

Compile, execute, correct! Since there will not be too many code blocks identified by braces, our macro can work whether there are braces after if or not! So, the do while in the previous example is really redundant? So we can happily release it again? If you are careful enough, you may have found the problem, which is the last semicolon above. Although there is no problem compiling and running the test, it is always a little dazzling? Yes, because when we write NSLog itself, we treat it as a statement, followed by a semicolon. After the macro is expanded, this semicolon is like a nightmare. Do you see anything else? Try to expand this example:

    
  1. if ( errorHappend )
  2. NSLog ( @"Oops, error happened" );
  3. else
  4. //Yep, no error, I am happy~ :)

No! I am not haapy at all! Because of compilation error! In fact, after the macro is expanded, it looks like this:

    
  1. if ( errorHappend ) {
  2. fprintf (( stderr , "<%s : %d> %s \n " ,[[[ NSString stringWithUTF8String : __FILE__ ] lastPathComponent ] UTF8String ], __LINE__ , __func__ );
  3. ( NSLog )(( format ), ## __VA_ARGS__ );
  4. fprintf ( stderr , "------- \n " );
  5. }; else {
  6. //Yep, no error, I am happy~ :)
  7. }

Because there are multiple sub numbers in front of else, which leads to a lot of annoyance Wouldn't it be all right if the coder wrote curly braces? But we still have a clever solution, that is, the above do while. Add the code block of the macro to do, and then while(0). There is no change in the behavior, but you can skillfully eat the semicolon of the tragedy. This is what happens after expanding with the version of do while

    
  1. if ( errorHappend )
  2. do {
  3. fprintf (( stderr , "<%s : %d> %s \n " ,[[[ NSString stringWithUTF8String : __FILE__ ] lastPathComponent ] UTF8String ], __LINE__ , __func__ );
  4. ( NSLog )(( format ), ## __VA_ARGS__ );
  5. fprintf ( stderr , "------- \n " );
  6. } while ( 0 );
  7. else {
  8. //Yep, no error, I am really happy~ :)
  9. }

This method of eating semicolons has been widely used in code block macros and has almost become a standard writing method. And the advantage of while(0) is that when compiling, the compiler will basically optimize for you and remove this part. The final compilation result will not lead to the difference in operation efficiency due to this do while. After finally understanding this strange do while, we can finally continue to go deep into this macro. The first line of the macro ontology content has nothing to say about fprintf (stderr, "<% s:% d >% s \ n" , simply format the output. Note that we used \ to divide this macro into several lines. In fact, it will be merged into the same line when it is expanded at the end. We also used the backslash at the end of MIN just now. I hope you can remember. In the next line, we fill in the three token s in this format output,

[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);

    

Three predefined macros are used here, which is the same as that just now__ COUNTER__ Similarly, the behavior of predefined macros is specified by the compiler__ FILE__ Returns the absolute path of the current file__ LINE__ Returns the number of lines in the file when the macro was expanded__ func__ Is the function name of the scope where the macro is changed. When we output the Log, if we take these three parameters, we can speed up the interpretation of the Log and locate it quickly. For the predefined Log of the compiler and some of their implementation mechanisms, interested students can move to the of gcc documents PreDefine page And clang's Builtin Macro View. Here, we set the three parameters of formatted output as the last part of the file name (because the absolute path is too long to look), the number of lines, and the method name.

The next step is to restore the original NSLog (NSLog) (format)##__ VA_ARGS__); Another predefined macro appears in__ VA_ARGS__ (we seem to have found out the rule. The front and rear double down bars are generally predefined)__ VA_ARGS__ Represents the macro definition of All remaining parameters in. As we said before, variable parameters will be handled uniformly. When expanding here, the compiler will__ VA_ARGS__ Directly replace with the remaining parameters in the input starting from the second parameter. Another suspense is that there are two well marks in front of it ##. Remember the two hash marks in MIN above, where the two hash marks mean to merge the first and second items. What we do here is similar. Merge the format string in front and the parameter list in the back, so that we can get a complete NSLog method. In the following lines, I believe you can understand it by yourself. Finally, output it and try it. It will probably look like this.

    
  1. -------
  2. < AppDelegate .m : 46> - [AppDelegate application:didFinishLaunchingWithOptions:]
  3. 2014 -01-20 16 :44 :25.480 TestProject [30466:70b] The array is (
  4. Hello,
  5. My,
  6. Macro
  7. )
  8. -------

Output with file, line number and method, and separated by horizontal bars (please forgive me for my lack of texture design, maybe I should draw a cow, like this?), It may be easier to debug:)

This Log has three suspense points. First, why should we write the format separately, and then pass other parameters as variable parameters? If we don't want that format and write it directly as NSLog (...) Is there a problem? For our example here, the words have not changed, but what we need to remember is Is a variable parameter list. It can represent one, two, or many parameters, but it can also represent zero parameters. If we directly use the parameter list without specifying the format parameter when declaring this macro, the NSLog() without writing parameters in use will also be matched to this macro, resulting in failure of compilation. If you have Xcode on hand, you can also look at the implementation of the real NSLog method in Cocoa. You can see that it is also in the form of receiving a format parameter and a parameter list. We define it in the macro just to pass in the correct and appropriate parameters, so as to ensure that users can use the macro correctly in the original way.

The second point is that since our variable parameters can accept any input, what happens when there is only one format input and the number of variable parameters is zero? Let's take a look. Remember that ## the function is before and after splicing, and now ## after the variable parameter is empty:

    
  1. NSLog(@ "Hello");
  2. => do {
  3. fprintf(( stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
  4. (NSLog)((@ "Hello"), );
  5. fprintf( stderr, "-------\n");
  6. } while ( 0);

The middle line (NSLog)(@"Hello",); There seems to be a problem. You must have doubts. How can it be compiled in this way?! It turned out that the great gods had already thought of this problem and made a special treatment. Here's a special rule between comma and__ VA_ARGS__ In addition to splicing the front and back text, the double pound sign also has a function, that is, if the rear text is empty, it will eat the front comma. This feature will take effect only when the above conditions are true, so it can be said to be a special case. After adding this rule, we can expand the formula just now to the correct (NSLog)(@"Hello"); Yes.

The last place worth discussing is (NSLog) (format)##__ VA_ ARGS__); Use parentheses. Remove the brackets that can be removed and write NSLog (format, ###_va_args_); Is it possible? There should be no big problem here. First, format will not be called many times, and there is little possibility of misuse (because finally, the compiler will check whether the NSLog input is correct). In addition, you don't have to worry that after expansion, the NSLog in the formula will be expanded again. Although the NSLog in the expansion also meets our macro definition, the macro expansion is very smart. If it will cycle infinitely after expansion, it won't be expanded again.

As a small reward for reading here, I hope you can use three macros of debug output rect, size and point (um... Think about how many times you have been tortured to death by printing a number of these structures, let them play! Of course, please add oil to understand them first)

    
  1. #define NSLogRect(rect) NSLog(@ "%s x:%.4f, y:%.4f, w:%.4f, h:%.4f", #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
  2. #define NSLogSize(size) NSLog(@ "%s w:%.4f, h:%.4f", #size, size.width, size.height)
  3. #define NSLogPoint(point) NSLog(@ "%s x:%.4f, y:%.4f", #point, point.x, point.y)

Two practical examples

Of course, it doesn't mean that the macros described above can't be used in practice. They are relatively simple to use as a starting point, but they are very suitable for entry. In fact, there are not so many strange problems in many macros we commonly use in our daily life. Many times we implement them according to our ideas, and then pay a little attention to the possible common problems introduced above, and a high-quality macro can be born. If you can write some meaningful and valuable macros, you have made a considerable contribution to the users of your code, the whole community, the whole world and reducing carbon emissions. Let's take a few practical examples to see how macros have changed our lives and the habit of writing code.

Let's take a look at these two macros first

    
  1. #define XCTAssertTrue(expression, format...) \
  2. _XCTPrimitiveAssertTrue(expression, ## format)
  3. #define _XCTPrimitiveAssertTrue(expression, format...) \
  4. ({ \
  5. @try { \
  6. BOOL _evaluatedExpression = !!(expression); \
  7. if (!_evaluatedExpression) { \
  8. _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 0, @#expression),format); \
  9. } \
  10. } \
  11. @catch (id exception) { \
  12. _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 1, @#expression, [exception reason]),format); \
  13. }\
  14. })

If you've been doing Apple Development for many years, but you haven't seen or don't know what XCTAssertTrue is, it's strongly recommended to learn about test driven development. I think it should be very helpful for you in the future. If you are already familiar with this command, let's start to see what happens behind the scenes.

With the above foundation, I believe you should be able to interpret this macro by yourself. ({...}) I'm familiar with the syntax and ## of. Here are three noteworthy points. At the beginning of this macro, the following parameters are format, This is actually a way of writing variable parameters, and And__ VA_ARGS__ Pairing is similar{ NAME}... Will be{ NAME} is paired. That is to say, the format of the macro content here refers to the definition, which first reverses the expression twice? I'm not a professional, but I still vaguely remember that it was said in the university program class that two operations of negation can ensure that the result is BOOL value, which is still important in objc (there have been many discussions on BOOL in objc. If you haven't been able to distinguish BOOL, bool and Boolean, you can refer to This article by NSHisper ). And then @#expression. We have been in contact with the double pound sign ##, and the operator we see here is the single pound sign #. Note that the @ before the pound sign is the compilation symbol of objc and does not belong to the object of macro operation. The function of a single pound sign is to string, To put it simply, add "at both ends" after replacement ", which is converted to a C string. Here @ is used, followed by #expression, and then an NSString with the content of expression is displayed. Then the NSString is passed as a parameter to _xctregisterfailureand _XCTFailureDescription, etc. continue to expand. These are the later words. At a simple glance, we can probably imagine how much the macro has saved us, If you write an assertion and need more than ten lines, your imagination will go crazy.

Another example is to find the people's favorite ReactiveCocoa(RAC) A macro definition in. For friends who are not familiar with or have not heard of RAC, you can simply have a look A series of related blog posts by Limboy (search for ReactiveCocoa), the introduction is great. If you think "Wow, this is cool and I want to learn", you might as well follow raywenderlich on this Series of tutorials Do some practice. It simply uses RAC, but it already includes the basic usage of Rac. There are several very important macros in RAC. They are the basic to ensure that Rac is concise and easy to use. It can be said that no one would like RAC without these macros. RACObserve is one of them. It creates a signal return for an attribute of the object through KVC (if you don't understand this sentence, don't worry, it has no impact on your understanding of the writing and expansion of this macro). For this macro, I decided not to expand and explain it as above. I will post all the relevant macros at the end. You might as well practice it to see if you can expand it to the state of the code and understand what happened. If you have any problems or experience in the process of development, please leave a message in the comments to share and exchange:)

Well, this article is long enough. I hope you won't be scared when you see macro after reading it. Instead, you can say happily that I will, I will, and I will. Of course, the ultimate goal is to write beautiful, efficient and concise macros, which will be very helpful in improving productivity or ~ intimidating your colleagues ~ improving your strength.

In addition, we must publicize those who have been paying attention to for a long time here @hangcom Senior Wu Hang's new book "reverse engineering of iOS applications". It's a great honor to be able to read the whole book with the permission of your predecessors before it is released. It can be said that you have enjoyed it. I didn't have any foundation for prison break development before, and I didn't know much about relevant fields. Under such a premise, the process of following the tutorials and examples in the book can be said to be very interesting. I have also been able to look at the iOS development industry I have been engaged in in in recent years from different perspectives and heights, and I have benefited a lot. It can be said that "reverse engineering of iOS applications" is a cool book I have read happily recently. Now the book is still on pre-sale, but it is close to the official sale on January 28. Interested students can go there Amazon perhaps ChinaPub I believe this book will be a great Spring Festival reading for iOS technicians.

Finally, the exercise we agreed to leave for you to play. I added some comments to help you understand what each macro does a little. A test field is left at the back of the article, which you can fill in and play with at will. Anyway, come on!

    
  1. //call RACSignal Is the name of the class
  2. RACSignal *signal = RACObserve(self, currentLocation);
  3. //The macro definition starts with
  4. //rac_valuesForKeyPath:observer: is the method name
  5. #define RACObserve(TARGET, KEYPATH) \
  6. [ (id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
  7. #define keypath(...) \
  8. metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
  9. //This macro obtains the keypath and determines whether the keypath exists during compilation to avoid miswriting
  10. //You don't have to mind the witchcraft here
  11. #define keypath1(PATH) \
  12. (((void)(NO && ((void)PATH, NO )), strchr(# PATH, '.' ) + 1 ))
  13. #define keypath2(OBJ, PATH) \
  14. (((void)(NO && ((void)OBJ.PATH, NO )), # PATH))
  15. //Whether A and B are equal. If they are equal, they will be expanded into the first item, otherwise they will be expanded into the second item
  16. //eg. metamacro_if_eq(0, 0 )(true)(false) => true
  17. // metamacro_if_eq(0, 1 )(true)(false) => false
  18. #define metamacro_if_eq(A, B) \
  19. metamacro_concat(metamacro_if_eq, A)(B)
  20. #define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
  21. #define metamacro_if_eq0(VALUE) \
  22. metamacro_concat(metamacro_if_eq0_, VALUE)
  23. #define metamacro_if_eq0_1(...) metamacro_expand_
  24. #define metamacro_expand_(...) __VA_ARGS__
  25. #define metamacro_argcount(...) \
  26. metamacro_at(20, __VA_ARGS__, 20 , 19 , 18 , 17 , 16 , 15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 )
  27. #define metamacro_at(N, ...) \
  28. metamacro_concat(metamacro_at, N)(__VA_ARGS__)
  29. #define metamacro_concat(A, B) \
  30. metamacro_concat_(A, B)
  31. #define metamacro_concat_(A, B) A ## B
  32. #define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
  33. #define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
  34. #define metamacro_head(...) \
  35. metamacro_head_(__VA_ARGS__, 0 )
  36. #define metamacro_head_(FIRST, ...) FIRST
  37. #define metamacro_dec(VAL) \
  38. metamacro_at(VAL, -1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 )
//Calling RACSignal is the name of the class RACSignal *signal = RACObserve(self, currentLocation);

Topics: C