为什么C中的void表示不无效?


25

在Java和C#等强类型语言中,void(或Void)作为方法的返回类型似乎意味着:

此方法不返回任何内容。没有。没有回报。您不会从此方法收到任何信息。

真正奇怪的是,在C语言中,void作为返回类型甚至方法参数类型的含义是:

真的可以是任何东西。您必须阅读源代码才能找到答案。祝好运。如果这是一个指针,那么您应该真的知道自己在做什么。

考虑以下C语言示例:

void describe(void *thing)
{
    Object *obj = thing;
    printf("%s.\n", obj->description);
}

void *move(void *location, Direction direction)
{
    void *next = NULL;

    // logic!

    return next;
}

显然,第二个方法返回一个指针,根据定义,该指针可以是任何东西。

由于C早于Java和C#,因此为什么这些语言采用void“ nothing”的含义,而C却使用“ nothing or any(当指针时)”呢?


138
您的问题标题和文本是void在代码示例使用时谈论的,void*这是完全不同的。

4
还要注意,Java和C#确实具有相同的概念,只是Object在一种情况下将其称为歧义即可。
Mooing Duck

4
@Mephy不是完全强类型吗?通过C#使用鸭子类型,您是dynamic说很少使用的类型吗?
Axarydax

9
@Mephy:我上次检查Wikipedia列出了“强类型”的11个相互矛盾的含义。因此说“ C#不是强类型”是没有意义的。
埃里克·利珀特

8
@LightnessRacesinOrbit:不,关键是该术语没有权威性的定义。维基百科不是一种说明性资源,它不能告诉您术语的正确含义是什么。它是一种描述性资源。它描述了11种不同的矛盾含义的事实,证明了如果不仔细定义该术语便无法在对话中使用。否则,这意味着对话中的人们很可能会过去,而不是互相交谈。
埃里克·利珀特

Answers:


58

关键字void(不是指针)在这些语言中表示“无”。这是一致的。

如您所述,void*在支持原始指针(C和C ++)的语言中表示“任何东西的指针”。这是一个不幸的决定,因为正如您提到的,它确实void意味着两个不同的意思。

我无法找到void在不同上下文中重用“无”和“任何”的背后的历史原因,但是C在其他几个地方也是如此。例如,static在不同的上下文中具有不同的目的。C语言显然有先例以这种方式重用关键字,而不管人们对此做法有何看法。

Java和C#的不同之处足以彻底解决这些问题。Java和“安全”的C#也不允许生指针,不需要容易与C兼容(不安全的C#确实让指针但绝大多数的C#代码不属于这一类)。这使他们无需担心向后兼容性就可以进行一些更改。一种做到这一点的方法是Object在所有类都从其继承的层次结构的根部引入一个类,因此Object引用具有相同的功能,void*而没有类型问题和原始内存管理的麻烦。


2
我到家时会提供参考

4
They also do not allow raw pointers and do not need easy C compatibility.假。C#具有指针。他们通常(通常应该离开),但在那里。
Magus 2014年

8
关于它们为什么被重用void:一个明显的原因是避免引入新的关键字(并可能破坏现有程序)。如果他们引入了一个特殊的关键字(例如unknown),那么void*它将是无意义的(并且可能是非法的)构造,并且unknown仅以形式合法unknown*
jamesdlin 2014年

20
即使在中void *,也void表示“没有”,而不是“任何”。您不能取消引用a void *,您需要先将其强制转换为另一种指针类型。
oefe 2014年

2
@oefe我认为拆分头发是没有意义的,因为voidvoid*是不同的类型(实际上void是“无类型”)。就像说“ intin int*意味着某些东西” 一样有意义。不,一旦声明了指针变量,您就说“这是一个能够保存某些内容的内存地址”。对于void*,您要说的是“此地址包含某些内容,但不能从指针的类型推断出某些内容。”

32

voidvoid*是两个不同的东西。 voidC中的含义与Java中的含义完全相同,只是没有返回值。A void*是没有类型的指针。

C中的所有指针都必须能够被取消引用。如果您取消引用a void*,那么您希望获得哪种类型?请记住,C指针不包含任何运行时类型信息,因此必须在编译时知道该类型。

在这种情况下,您可以在逻辑上对取消引用的操作做的唯一事情void*就是忽略它,这正是该void类型所表示的行为。


1
我同意,直到您了解“忽略它”为止。返回的事物可能包含有关如何解释它的信息。换句话说,它可能是BASIC Variant的C等效项。“当你得到这个时,看看它是什么-我不能告诉你你会发现什么。”
弗洛里斯2014年

1
或者换一种说法,假设我可以将程序员定义的“类型描述符”放在指针寻址的存储位置的第一个字节中,这将告诉我可以在随后的字节中找到哪种数据类型。
罗伯特·哈维

1
当然,但是在C语言中,决定将这些细节留给程序员,而不是将其融入语言中。从语言的角度来看,除了忽略之外,它不知道还能做什么。这样可以避免在大多数情况下可以静态确定类型信息时始终将类型信息作为第一个字节的开销。
Karl Bielefeldt 2014年

4
@RobertHarvey是的,但是您并没有取消引用空指针;您将void指针强制转换为char指针,然后取消引用char指针。
user253751 2014年

4
@Floris gcc与您不同意。如果尝试使用该值,则gcc产生一条错误消息,提示您error: void value not ignored as it ought to be
kasperd 2014年

19

将其void视为返回类型可能会更有用。然后,您的第二个方法将显示为“返回无类型指针的方法”。


有帮助。经过多年使用面向对象语言的学习(最终!),C指针对我来说是一个非常新的概念。似乎在返回指针时,void大致相当于*ActionScript 3之类的语言,它仅表示“任何返回类型”。
Naftuli Kay 2014年

5
好吧,void这不是通配符。指针只是一个内存地址。将类型放在*“这是您可以期望在此指针引用的存储器地址上找到的数据类型” 之前。把void之前*说要编译“我不知道是什么类型,只返回我的原始指针,我会找出与它指向的数据做的。”
罗伯特·哈维

2
我什至会说void*一个“无效”点。几乎没有指向的裸指针。仍然可以像其他任何指针一样强制转换它。
罗伯特

11

让我们收紧术语。

根据在线C 2011标准

6.2.5类型
...
19 void类型包含一组空值;这些值包含一个空值。它是无法完成的不完整对象类型。
...
6.3转换
...
6.3.2.2 void

1 不得以任何方式使用void表达式(具有类型的表达式)的(不存在)值,并且void不得将隐式或显式转换(除外void)应用于这样的表达。如果将任何其他类型的void 表达式作为表达式求值,则将其值或指示符丢弃。(对void表达式的副作用进行评估。)

6.3.2.3指针

1指向的指针void可以在任何对象类型的指针之间进行转换。指向任何对象类型的指针都可以转换为void并再次返回的指针;结果应等于原始指针。

void表达式没有值(虽然它可能有副作用)。如果我有一个定义为return的函数void,如下所示:

void foo( void ) { ... }

然后打电话

foo();

不能求值;我无法将结果分配给任何东西,因为没有结果。

指针void基本上是一个“通用”的指针类型; 您可以void *为任何其他对象指针类型分配一个值,而无需显式强制转换(这就是为什么所有C程序员都会对您大喊以强制转换结果malloc)。

您不能直接取消引用a void *; 您必须先将其分配给其他对象指针类型,然后才能访问指向的对象。

void指针用于实现(或多或少)通用接口;典型的例子是qsort库函数,只要您提供类型识别比较函数,它就可以对任何类型的数组进行排序。

是的,对于两个不同的概念(无值与通用指针)使用相同的关键字会造成混淆,但这并不意味着没有先例。static在C和C ++中都有多个不同的含义。


9

在Java和C#中,void作为方法的返回类型似乎意味着:此方法不返回任何内容。没有。没有回报。您不会从此方法收到任何信息。

那句话是正确的。对于C和C ++也是正确的。

在C语言中,void作为返回类型甚至方法参数类型都意味着不同。

那句话是不正确的。void作为C或C ++中的返回类型,其含义与在C#和Java中相同。你是混淆voidvoid*他们是完全不同的。

是不是这样的困惑,voidvoid*表示两种完全不同的东西?

是的

什么是指针?什么是空指针?

指针是一个,其可以解除引用。解引用一个有效的指针给出了一个存储位置的中指向的类型。甲void指针是具有没有特别指向的键入一个指针; 在取消引用该值以生成存储位置之前,必须将其转换为更特定的指针类型。

空指针在C,C ++,Java和C#中是否相同?

Java没有空指针。C#可以。它们与C和C ++中的相同-指针值没有任何关联的特定类型,在取消引用它以产生存储位置之前必须将其转换为更特定的类型。

由于C早于Java和C#,所以为什么这些语言为什么将void表示为“无”,而C则将其用作“无或什么(当有指针时)”?

这个问题是不连贯的,因为它以谎言为前提。让我们问一些更好的问题。

为什么Java和C#会采用void有效返回类型的约定,而不是例如使用Visual Basic约定(函数从不返回空值而子例程始终是返回空值)呢?

熟悉从void返回类型的语言中使用Java或C#的程序员。

为什么C#采用了void*与含义完全不同的令人困惑的约定void

对于使用void*指针类型的语言来使用C#的程序员来说很熟悉。


5
void describe(void *thing);
void *move(void *location, Direction direction);

第一个函数不返回任何内容。第二个函数返回一个空指针。我会宣布第二个功能为

void* move(void* location, Direction direction);

如果将“ void”理解为“无意义”可能会有所帮助。返回值describe是“无意义”。从这样的函数返回值是非法的,因为您告诉编译器返回值是没有意义的。您无法从该函数捕获返回值,因为它没有任何意义。您不能声明类型的变量,void因为它没有意义。例如void nonsense;胡说八道,实际上是非法的。另请注意,void数组void void_array[42];也是胡说八道。

指向void(void*)的指针有所不同。它是一个指针,而不是数组。读void*为“无意义”的指针。指针是“无意义的”,这意味着取消引用这样的指针是没有意义的。试图这样做实际上是非法的。取消引用空指针的代码将无法编译。

因此,如果您不能取消引用空指针,并且不能创建空数组,那么如何使用它们呢?答案是可以将指针指向任何类型void*。将一些指针void*强制转换为原始类型的指针,然后再转换回原始类型的指针,将产生一个等于原始指针的值。C标准保证了这种行为。您在C中看到如此多的空指针的主要原因是,此功能提供了一种以非常非面向对象的语言来实现信息隐藏和基于对象的编程的方法。

最后,之所以你经常看到move声明void *move(...),而不是void* move(...),是因为把旁边的星号名称,而不是类型是C.一个非常普遍的做法的原因是,下面的声明将让你在大麻烦:int* iptr, jptr;这会使你我想你在声明两个指向一个指针的指针int。你不是。这个声明使得jptr一个int,而不是一个指针int。正确的声明是int *iptr, *jptr;,如果您坚持每个声明语句仅声明一个变量的做法,则可以假装星号属于该类型。但是请记住,您是假装的。


“取消引用空指针的代码将无法编译。” 我认为您的意思是取消引用指针的代码将无法编译。编译器不会保护您不要取消引用空指针;那是一个运行时问题。
科迪·格雷

@CodyGray-谢谢!修复了。取消引用空指针的代码将进行编译(只要该指针不是void* null_ptr = 0;)。
David Hammen 2014年

2

简而言之,没有区别。让我给你一些例子。

假设我有一堂课叫汽车。

Car obj = anotherCar; // obj is now of type Car
Car* obj = new Car(); // obj is now a pointer of type Car
void obj = 0; // obj has no type
void* = new Car(); // obj is a pointer with no type

我相信这里的困惑是基于您void对vs的定义void*。返回类型的void意思是“不返回任何内容”,而返回类型的void*意思是“不返回任何内容的指针”。

这是由于指针是一种特殊情况。指针对象将始终指向内存中的某个位置,无论那里是否存在有效的对象。无论我void* car引用一个字节,一个字,一个汽车或自行车并不重要,因为我void*的东西没有定义的类型。我们需要稍后对其进行定义。

在诸如C#和Java之类的语言中,管理内存,并且将指针的概念翻转到Object类中。我们没有引用“ nothing”类型的对象,而是知道我们的对象至少是“ Object”类型的。让我解释一下原因。

在传统的C / C ++中,如果创建对象并删除其指针,则无法访问该对象。这就是所谓的内存泄漏

在C#/ Java中,一切都是对象,这使运行时可以跟踪每个对象。不一定需要知道我的对象是Car,它只需要知道Object类已经处理的一些基本信息。

对于我们的程序员来说,这意味着我们必须在C / C ++中手动处理的许多信息都由Object类来处理,而无需使用特殊的指针(指针)。


“我的void *指向没有定义类型的东西”-不完全是。可以说它是一个零长度值的指针(几乎就像一个具有0个元素的数组,甚至没有与数组关联的类型信息),这是更正确的说法。
Scott Whitlock 2014年

2

我将尝试说出人们如何用C来思考这些事情。C标准中的官方语言对并不是很轻松void,并且我不会尝试与之完全一致。

该类型void不是这些类型中的头等公民。尽管它是对象类型,但不能是任何对象(或值),字段或函数参数的类型。但是,它可以是函数的返回类型,从而可以是表达式的类型(基本上是此类函数的调用,但是由条件运算符形成的表达式也可以具有void类型)。但即使是使用最少的void为价值型,上面的叶子的门打开,即结束了函数返回void与形式的语句return E;,其中E是类型的表达式void,用C明确禁止(被允许在C ++中,虽然,但由于不适用于C)的原因。

如果void允许使用类型的对象,则它们将具有0位(这是void类型的表达式的值中的信息量);允许这样的对象并不是一个主要问题,但是它们将毫无用处(大小为0的对象在定义指针算术时会遇到一些困难,最好是禁止这样做)。一组不同的值,这样一个对象将具有一个(琐碎的)元素;它的值可以被取用,因为它没有任何实质内容,但是却不能修改(缺少任何其他值)。因此,该标准的混乱之处在于void它包含一组空值;这样的类型对于描述不能尽管C语言实际上并没有采用这种类型,但可以对其进行评估(例如跳转或非终止调用)。

不允许将其作为值类型void使用,至少以两种不直接相关的方式来指定“禁止”:(void)在函数类型中指定为参数规范意味着禁止向其提供任何参数(而“()”相反意味着一切是允许的;是的,甚至禁止将void表达式作为带有(void)参数指定的函数的参数提供),并且声明void *p意味着*p禁止使用反引用表达式(但不表示它是type的有效表达式void)。因此,您对这些用法的使用voidvoid一种有效的表达式类型实际上并不一致。但是类型的对象void*实际上不是真正指向任何类型的值的指针,它意味着尽管无法取消引用并且禁止使用指针进行算术运算,但该值被视为指针。那么,“视为指针”实际上仅意味着它可以在任何指针类型之间来回转换而不会丢失信息。但是,它也可以用于在整数值之间来回转换,因此根本不需要指向任何东西。


严格地说,它可以转换从和任何对象指针类型而不会丢失信息,但并不总是往返。通常,从void*类型转换为另一种指针类型可能会丢失信息(甚至有UB,我记不起来了),但是如果该值void*来自于转换您现在要转换为相同类型的指针,则没有。
史蒂夫·杰索普

0

在C#和Java中,每个类都派生自一个Object类。因此,每当我们希望传递对“某物”的引用时,都可以使用type的引用Object

由于我们不需要在这些语言中为此目的使用void指针,因此在void这里不必表示“什么也不是”。


0

在C语言中,可以有一个指向任何类型的事物的指针[指针的行为相当于引用的类型]。指针可以标识已知类型的对象,也可以标识任意(未知)类型的对象。C的创建者选择使用与“指向某种特定类型的事物的指针”相同的通用语法。由于在后一种情况下的语法要求使用标记来指定类型,因此在前一种语法中,如果类型已知,则需要在标记应属于的位置放置一些内容。为此,C选择使用关键字“ void”。

在Pascal中,与C一样,可以指向任何类型的事物。Pascal更流行的方言还允许“指向任意类型的事物的指针”,但是与其使用与“指向某种特定类型的事物的指针”所使用的语法相同,它们只是将前者称为Pointer

在Java中,没有用户定义的变量类型。一切都是原始或堆对象引用。可以定义类型为“对特定类型的堆对象的引用”或“对任意类型的堆对象的引用”的事物。前者只是使用类型名称,而没有任何特殊的标点符号来表示它是一个引用(因为它不能是其他任何东西);后者使用类型名称Object

采用void*的命名为一个指向任意类型的东西,只要我知道的唯一的C和直接衍生物如C ++; 我所知道的其他语言都可以通过使用唯一命名的类型来处理该概念。


-1

您可以将关键字void想成一个空struct在C99中,显然不允许使用空结构,在.NET和C ++中,它们占用的内存超过0,最后我记得Java中的所有内容都是一个类):

struct void {
};

...这是长度为0的类型。这是当您执行返回void... 的函数调用时的工作方式,而不是在堆栈上为整数返回值分配4个字节左右,它根本不会改变堆栈指针(将其长度增加0) )。

因此,如果您声明了一些变量,例如:

int a;
void b;
int c;

...然后获取这些变量的地址,然后也许b并且c可能位于内存中的同一位置,因为b长度为零。因此,a void*是内存中指向零长度内置类型的指针。您可能知道紧随其后的某件事可能对您有用的事实是另一回事,并且要求您真正了解自己在做什么(这很危险)。


仅有的编译器,我知道受让人的长度键入空隙是gcc,和gcc受让人它为1的长度
约书亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.