最近,我正在阅读JOS Kernel的代码(在麻省理工学院开发,主要是为了帮助像我这样的初学者学习目的),并提出一个小小的疑问,我认为这可能是微不足道的但是无法弄清楚这样发布在这里求助..
这是来自" .c" 文件的一小段代码: -
if(n>0)
{
nextfree = ROUNDUP((char *) nextfree, PGSIZE);
result=nextfree;
nextfree+=n;
PADDR(nextfree);
}
对应的" .h" 文件: -
/* This macro takes a kernel virtual address -- an address that points above
* KERNBASE, where the machine's maximum 256MB of physical memory is mapped --
* and returns the corresponding physical address. It panics if you pass it a
* non-kernel virtual address.
*/
#define PADDR(kva) \
({ \
physaddr_t __m_kva = (physaddr_t) (kva); \
if (__m_kva < KERNBASE) \
panic("PADDR called with invalid kva %08lx", __m_kva);\
__m_kva - KERNBASE; \
})
现在我对上述结构有两个问题 -
我们不应该将PADDR(nextfree)
的值分配给某个变量,例如var=PADDR(nextfree)
,而不是像上面那样直接调用它。它将如何运作?
为什么有人喜欢在头文件中编写如此小而复杂的定义,而不是为易于掌握的指定任务创建函数。
答案 0 :(得分:4)
当您调用宏时,编译器会在此时将宏定义替换为您的代码。除非宏恰好扩展为具有返回值的东西,否则没有“返回值”。
这个结构:
( { /* ... */ } )
是一个特定于gcc的扩展,称为“语句表达式”,记录为here。它由括在括号中的复合语句组成,它产生最后一个表达式的值。 (如果; }
之前的最后一件事不是表达式,那么整件事就不会产生值。)
PADDR()
宏获取内核虚拟地址kva
并生成相应的物理地址。如果虚拟地址无效,则会发生混乱。 (它可能是作为一个函数编写的,但是作者选择使用宏,可能是为了效率。inline
函数可能达到了相同的目标。)
在您展示的使用PADDR
:
if (n > 0) {
/* snip */
PADDR(nextfree);
}
调用PADDR
宏,但丢弃它产生的值。假设这不是错误,如果nextfree
不是有效的虚拟地址,则可能会强制执行此操作。代码不使用生成的物理地址,因为它不需要它;检查就是它所需要的。
它仍然计算__m_kva - KERNBASE;
,这可能有点浪费,但我怀疑成本是否很高 - 并且优化编译器可能会认识到结果未被使用,并且丢弃计算。 / p>
答案 1 :(得分:1)
嗯......取消这个:
__m_kva - KERNBASE
。所以回答问题1:它确实返回一个值,但是在你的示例代码片段中,这个返回的值根本就不用了。但是,据推测,调用宏的其他代码确实使用了该值。回答问题2:这取决于。你可以用宏来做一些你根本不能用函数做的事情,比如它们undefining
。在这种情况下,程序员似乎需要复制常见的错误检查,并使用转换宏在示例代码段中执行此操作。 (它提前检查下一个空闲不会无意中尝试free
用户空间内存或类似内容。)