【问题标题】:Why is it necessary to cast NULL to a type in this macro?为什么必须将 NULL 强制转换为该宏中的类型?
【发布时间】:2014-06-07 20:17:37
【问题描述】:

我对 Eric Roberts 的 Programming Abstractions in C 中的一些代码有疑问。他使用自己的几个库来为读者简化事情并教授如何编写库。 (本书的所有图书馆代码都可以在on this site找到。)

一个库genlib 提供了一个宏,用于泛型分配指向struct 类型的指针。我不明白宏的一部分。我将复制下面的代码,以及如何使用它的示例,然后我将更详细地解释我的问题。

/*
 * Macro: New
 * Usage: p = New(pointer-type);
 * -----------------------------
 * The New pseudofunction allocates enough space to hold an
 * object of the type to which pointer-type points and returns
 * a pointer to the newly allocated pointer.  Note that
 * "New" is different from the "new" operator used in C++;
 * the former takes a pointer type and the latter takes the
 * target type.
 */

#define New(type) ((type) GetBlock(sizeof *((type) NULL)))

/* GetBlock is a wrapper for malloc. It encasulates the 
 * common sequence of malloc, check for NULL, return or
 * error out, depending on the NULL check. I'm not going
 * to copy that code since I'm pretty sure it isn't
 * relevant to my question. It can be found here though:
 * ftp://ftp.awl.com/cseng/authors/roberts/cs1-c/standard/genlib.c
 */

Roberts 打算将代码按如下方式使用:

    typedef struct {
        string name;
        /* etc. */
    } *employeeT;
    employeeT emp;
    emp = New(employeeT);

他更喜欢使用指向记录的指针作为类型名称,而不是记录本身。所以New 提供了一种通用的方式来分配这样的struct 记录。

在宏New中,我不明白的是:sizeof *((type)) NULL)。如果我没看错,它会说“将NULL 的取消引用转换的大小取为struct 类型type 在给定调用中表示的任何值”。我想我理解取消引用:我们想为结构分配足够的空间;指针的大小不是我们需要的,所以我们取消引用以获得底层记录类型的大小。但我不明白将NULL 转换为类型的想法。

我的问题:

  1. 你可以投NULL吗?这到底是什么意思?
  2. 为什么需要演员表?当我尝试删除它时,编译器显示error: expected expression。那么,sizeof *(type) 不是表达式吗?这让我很困惑,因为我可以执行以下操作来获取任意指向结构的指针的大小:

    #define struct_size(s_ptr) do { \
        printf("sizeof dereferenced pointer to struct %s: %lu\n", \
               #s_ptr, sizeof *(s_ptr)); \
    } while(0)
    

编辑:正如许多人在下面指出的那样,这两个示例并不相同:

/* How genlib uses the macro. */
New(struct MyStruct*)
/* How I was using my macro. */
struct MyStruct *ptr; New(ptr)

郑重声明,这不是家庭作业。我是一个试图提高 C 语言的业余爱好者。此外,据我所知,代码没有问题。也就是说,我不是在问如何用它做一些不同的事情。我只是想更好地理解 (1) 它是如何工作的以及 (2) 为什么它必须按原样编写。谢谢。

【问题讨论】:

  • 如果s_ptr 不是表达式,则您的struct_size 宏无效。如果您将struct MyStruct * 传递为s_ptr,则代码将无法编译,因为sizeof *(struct MyStruct *) 不是有效的C... 而且不,*(type) 不是表达式,因为它没有意义——究竟是什么您是否期望 *(type) 评估为?
  • @FilipeGonçalves 对于我的预期,我没有一个好的答案。我很困惑。这就是问题所在。

标签: c macros struct null expression


【解决方案1】:

问题是宏需要获取指针类型所指向的类型的大小。

例如,假设您有指针类型struct MyStruct*。如果不从该表达式中删除星号,您将如何获得struct MyStruct 的大小?你不会写

sizeof(*(struct MyStruct*))

因为那不是合法的 C 代码。

另一方面,如果你有一个struct MyStruct* 类型的变量,你可以这样做:

struct MyStruct* uselessPointer;
sizeof(*uselessPointer);

由于sizeof 并不实际评估其参数(它只是确定表达式类型的静态大小),因此这是安全的。

当然,在宏中,您不能定义新变量。但是,您可以通过强制转换现有指针来构成指向struct MyStruct* 的随机指针。在这里,NULL 是一个很好的候选 - 它是一个现有的指针,您可以合法地将其转换为 struct MyStruct*。因此,如果你要写

sizeof(* ((struct MyStruct*)NULL))

代码会

  1. NULL 转换为struct MyStruct*,产生一个静态类型的指针struct MyStruct*
  2. 确定将通过取消引用指针形成的对象的大小。由于指针的类型为struct MyStruct*,它指向struct MyStruct 类型的对象,所以这会产生struct MyStruct 的类型。

换句话说,这是一种获取指针类型对象的简单方法,以便您可以取消引用它并获取底层类型的对象。

我曾与 Eric 合作过其他一些宏,他是预处理器的真正专家。我对这行得通并不感到惊讶,我对它的复杂性并不感到惊讶,但它确实很聪明!

请注意 - 在 C++ 中,这种技巧过去很常见,直到引入了 declval 实用程序类型,这是该操作的一个不太复杂的版本。

希望这会有所帮助!

【讨论】:

  • 总体来说很有帮助。我唯一剩下的问题(与 Oli 的回答一样)是为什么 *(type) 本身不能由 sizeof 评估。在我愚蠢的宏中(稍后在问题中),我在调用printf 时这样做。两者有何不同?
  • 不能sizeof内部进行评估没有根本原因,但语言不允许这样做。 sizeof 的唯一选项是 sizeof(type-name)sizeof expression,它们不能混用。
  • 在我的宏中使用printf 不是表达式吗?也许这就是我不确定的。 为什么*(type)不是表达式?
  • @Telemachus 我不确定你的宏是否有效。如果你传入一个实际的指针,那么它可以正常工作,因为*(s_ptr) 表示“取消引用s_ptr”如果你传入一个类型名,它不会在我的系统上编译。
  • 我传入指针。我可以确认我的作品适用于该用例:gist.github.com/telemachus/62c72005e8d514c58014。但这也是罗伯茨使用宏的方式(或一种方式)。所以我不明白相关的区别。
【解决方案2】:

这是一个黑客。它依赖于 sizeof 运算符的参数实际上没有被评估这一事实。

回答您的具体问题:

  1. 是的,NULL 只是一个指针文字。像任何其他指针一样,它可以被强制转换。

  2. sizeof 对类型或表达式进行操作。 *(type) 两者都不是(发生宏替换后),这将是语法错误。

【讨论】:

  • 你能多说一点why *(type) 既不是类型也不是值?在问题后面的愚蠢宏中,我在 printf 调用中使用了等价物,不是吗?那有什么不同?
  • @Telemachus:大概是因为在那个例子中,s_ptr 是一个表达式,而不是一个类型?
  • 是的。正如我在对 templatetypedef 答案的最后评论中所说的那样,我没有充分考虑传递类型名称和指向该类型的实际指针之间的区别。我接受了他更全面的回答,但非常感谢你的回答。这很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2020-06-04
  • 2011-12-16
  • 2018-05-24
  • 2011-12-28
相关资源
最近更新 更多