C语言 C中的三重指针:这是风格问题吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21488544/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Triple pointers in C: is it a matter of style?
提问by darda
I feel like triple pointers in C are looked at as "bad". For me, it makes sense to use them at times.
我觉得 C 中的三重指针被视为“坏”。对我来说,有时使用它们是有意义的。
Starting from the basics, the single pointerhas two purposes: to create an array, and to allow a function to change its contents (pass by reference):
从基础开始,单指针有两个目的:创建数组,以及允许函数更改其内容(通过引用传递):
char *a;
a = malloc...
or
或者
void foo (char *c); //means I'm going to modify the parameter in foo.
{ *c = 'f'; }
char a;
foo(&a);
The double pointercan be a 2D array (or array of arrays, since each "column" or "row" need not be the same length). I personally like to use it when I need to pass a 1D array:
在双指针可以是2D阵列(或阵列的阵列中,由于每个“列”或“行”不必具有相同的长度)。我个人喜欢在需要传递一维数组时使用它:
void foo (char **c); //means I'm going to modify the elements of an array in foo.
{ (*c)[0] = 'f'; }
char *a;
a = malloc...
foo(&a);
To me, that helps describe what foo is doing. However, it is not necessary:
对我来说,这有助于描述 foo 正在做什么。但是,没有必要:
void foo (char *c); //am I modifying a char or just passing a char array?
{ c[0] = 'f'; }
char *a;
a = malloc...
foo(a);
will also work.
也会起作用。
According to the first answer to this question, if foowere to modify the size of the array, a double pointer would be required.
根据这个问题的第一个答案,如果foo要修改数组的大小,则需要一个双指针。
One can clearly see how a triple pointer(and beyond, really) would be required. In my case if I were passing an array of pointers (or array of arrays), I would use it. Evidently it would be required if you are passing into a function that is changing the size of the multi-dimensional array. Certainly an array of arrays of arrays is not too common, but the other cases are.
人们可以清楚地看到如何需要三重指针(甚至更多)。在我的情况下,如果我传递一个指针数组(或数组数组),我会使用它。显然,如果您要传入一个更改多维数组大小的函数,则需要这样做。当然数组数组的数组不是太常见,但其他情况是。
So what are some of the conventions out there? Is this really just a question of style/readability combined with the fact that many people have a hard time wrapping their heads around pointers?
那么有哪些约定俗成的呢?这真的只是一个风格/可读性的问题,再加上许多人很难把头放在指针上吗?
回答by milleniumbug
Using triple+ pointers is harming both readability and maintainability.
使用三重指针会损害可读性和可维护性。
Let's suppose you have a little function declaration here:
假设您在这里有一个小函数声明:
void fun(int***);
Hmmm. Is the argument a three-dimensional jagged array, or pointer to two-dimensional jagged array, or pointer to pointer to array (as in, function allocates an array and assigns a pointer to int within a function)
嗯。参数是一个三维交错数组,还是指向二维交错数组的指针,还是指向数组指针的指针(如,函数分配一个数组并在函数内分配一个指向 int 的指针)
Let's compare this to:
让我们将其与:
void fun(IntMatrix*);
Surely you can use triple pointers to int to operate on matrices. But that's not what they are. The fact that they're implemented here as triple pointers is irrelevantto the user.
当然,您可以使用指向 int 的三重指针来操作矩阵。但那不是他们的样子。它们在这里作为三重指针实现的事实与用户无关。
Complicated data structures should be encapsulated. This is one of manifest ideas of Object Oriented Programming. Even in C, you can apply this principle to some extent. Wrap the data structure in a struct (or, very common in C, using "handles", that is, pointers to incomplete type - this idiom will be explained later in the answer).
复杂的数据结构应该被封装。这是面向对象编程的明显思想之一。即使在 C 中,您也可以在某种程度上应用此原则。将数据结构包装在一个结构中(或者,在 C 中非常常见,使用“句柄”,即指向不完整类型的指针 - 这个习语将在后面的答案中解释)。
Let's suppose that you implemented the matrices as jagged arrays of double. Compared to contiguous 2D arrays, they are worse when iterating over them (as they don't belong to a single block of contiguous memory) but allow for accessing with array notation and each row can have different size.
假设您将矩阵实现为 的锯齿状数组double。与连续的 2D 数组相比,它们在迭代时更糟(因为它们不属于单个连续内存块),但允许使用数组符号进行访问,并且每行可以具有不同的大小。
So now the problem is you can't change representations now, as the usage of pointers is hard-wired over user code, and now you're stuck with inferior implementation.
所以现在的问题是你现在不能改变表示,因为指针的使用是通过用户代码硬连接的,现在你被困在低劣的实现中。
This wouldn't be even a problem if you encapsulated it in a struct.
如果您将其封装在结构中,这甚至不会成为问题。
typedef struct Matrix_
{
double** data;
} Matrix;
double get_element(Matrix* m, int i, int j)
{
return m->data[i][j];
}
simply gets changed to
只是被更改为
typedef struct Matrix_
{
int width;
double data[]; //C99 flexible array member
} Matrix;
double get_element(Matrix* m, int i, int j)
{
return m->data[i*m->width+j];
}
The handle technique works like this: in the header file, you declare a incomplete struct and all the functions that work on the pointer to the struct:
句柄技术是这样工作的:在头文件中,你声明一个不完整的结构体和所有作用于结构体指针的函数:
// struct declaration with no body.
struct Matrix_;
// optional: allow people to declare the matrix with Matrix* instead of struct Matrix*
typedef struct Matrix_ Matrix;
Matrix* create_matrix(int w, int h);
void destroy_matrix(Matrix* m);
double get_element(Matrix* m, int i, int j);
double set_element(Matrix* m, double value, int i, int j);
in the source file you declare the actual struct and define all the functions:
在源文件中声明实际结构并定义所有函数:
typedef struct Matrix_
{
int width;
double data[]; //C99 flexible array member
} Matrix;
double get_element(Matrix* m, int i, int j)
{
return m->data[i*m->width+j];
}
/* definition of the rest of the functions */
The rest of the world doesn't know what does the struct Matrix_contain and it doesn't know the size of it. This means users can't declare the values directly, but only by using pointer to Matrixand the create_matrixfunction. However, the fact that the user doesn't know the size means the user doesn't depend on it - which means we can remove or add members to struct Matrix_at will.
世界其他地方不知道它struct Matrix_包含什么,也不知道它的大小。这意味着用户不能直接声明值,而只能使用指向Matrix和create_matrix函数的指针。然而,用户不知道大小的事实意味着用户不依赖它 - 这意味着我们可以随意删除或添加成员struct Matrix_。
回答by Lundin
Most of the time, the use of 3 levels of indirection is a symptom of bad design decisions made elsewhere in the program. Therefore it is regarded as bad practice and there are jokes about "three star programmers" where, unlike the the rating for restaurants, more stars means worse quality.
大多数情况下,使用 3 级间接是程序中其他地方做出错误设计决策的征兆。因此它被认为是不好的做法,并且有关于“三星级程序员”的笑话,与餐厅的评级不同,星级越多意味着质量越差。
The need for 3 levels of indirection often originates from the confusion about how to properly allocate multi-dimensional arrays dynamically. This is often taught incorrectly even in programming books, partially because doing it correctly was burdensome before the C99 standard. My Q&A post Correctly allocating multi-dimensional arraysaddresses that very issue and also illustrates how multiple levels of indirection will make the code increasingly hard to read and maintain.
对 3 级间接的需求通常源于对如何正确动态分配多维数组的困惑。即使在编程书籍中,这也经常被错误地教导,部分原因是在 C99 标准之前正确地执行它是一种负担。我的问答帖子正确分配多维数组解决了这个问题,并说明了多个间接级别将如何使代码越来越难以阅读和维护。
Though as that post explains, there are some situations where a type**might make sense. A variable table of strings with variable length is such an example. And when that need for type**arises, you might soon be tempted to use type***, because you need to return your type**through a function parameter.
尽管正如那篇文章所解释的那样,在某些情况下 atype**可能有意义。具有可变长度的字符串的可变表就是这样一个例子。当type**出现这种需求时,您可能很快就会尝试使用type***,因为您需要type**通过函数参数返回您的。
Most often this need arises in a situation where you are designing some manner of complex ADT. For example, lets say that we are coding a hash table, where each index is a 'chained' linked list, and each node in the linked list an array. The proper solution then is to re-design the program to use structs instead of multiple levels of indirection. The hash table, linked list and array should be distinct types, autonomous types without any awareness of each other.
大多数情况下,这种需求出现在您设计某种复杂 ADT 的情况下。例如,假设我们正在编写一个哈希表,其中每个索引都是一个“链式”链表,链表中的每个节点都是一个数组。正确的解决方案是重新设计程序以使用结构而不是多个间接级别。哈希表、链表和数组应该是不同的类型,相互之间没有任何意识的自治类型。
So by using proper design, we will avoid the multiple stars automatically.
因此,通过使用适当的设计,我们将自动避免多颗星。
But as with every rule of good programming practice, there are always exceptions. It is perfectly possible to have a situation like:
但正如所有良好编程实践的规则一样,总有例外。完全有可能出现以下情况:
- Must implement an array of strings.
- The number of strings is variable and may change in run-time.
- The length of the strings is variable.
- 必须实现一个字符串数组。
- 字符串的数量是可变的,可能会在运行时发生变化。
- 字符串的长度是可变的。
You canimplement the above as an ADT, but there may also be valid reasons to keep things simple and just use a char* [n]. You then have two options to allocate this dynamically:
您可以将上述内容实现为 ADT,但也可能有正当理由让事情变得简单并仅使用char* [n]. 然后,您有两个选项可以动态分配:
char* (*arr_ptr)[n] = malloc( sizeof(char*[n]) );
or
或者
char** ptr_ptr = malloc( sizeof(char*[n]) );
The former is more formally correct, but also cumbersome. Because it has to be used as (*arr_ptr)[i] = "string";, while the alternative can be used as ptr_ptr[i] = "string";.
前者在形式上更正确,但也很麻烦。因为它必须用作(*arr_ptr)[i] = "string";,而替代品可以用作ptr_ptr[i] = "string";。
Now suppose we have to place the malloc call inside a function andthe return type is reserved for an error code, as is custom with C APIs. The two alternatives will then look like this:
现在假设我们必须将 malloc 调用放在一个函数中,并且返回类型是为错误代码保留的,就像 C API 的自定义一样。这两种选择将如下所示:
err_t alloc_arr_ptr (size_t n, char* (**arr)[n])
{
*arr = malloc( sizeof(char*[n]) );
return *arr == NULL ? ERR_ALLOC : OK;
}
or
或者
err_t alloc_ptr_ptr (size_t n, char*** arr)
{
*arr = malloc( sizeof(char*[n]) );
return *arr == NULL ? ERR_ALLOC : OK;
}
It is quite hard to argue and say that the former is more readable, and it also comes with the cumbersome access needed by the caller. The three star alternative is actually more elegant, in this very specific case.
很难争辩说前者更具可读性,而且它还伴随着调用者所需的繁琐访问。在这种非常特殊的情况下,三星级替代方案实际上更优雅。
So it does us no good to dismiss 3 levels of indirection dogmatically. But the choice to use them must be well-informed, with an awareness that they may create ugly code and that there are other alternatives.
因此,教条地驳回 3 个间接级别对我们没有好处。但是选择使用它们必须是知情的,并意识到它们可能会创建丑陋的代码并且还有其他替代方案。
回答by John Bode
So what are some of the conventions out there? Is this really just a question of style/readability combined with the fact that many people have a hard time wrapping their heads around pointers?
那么有哪些约定俗成的呢?这真的只是一个风格/可读性的问题,再加上许多人很难把头放在指针上吗?
Multiple indirection is not bad style, nor black magic, and if you're dealing with high-dimension data then you're going to be dealing with high levels of indirection; if you're really dealing with a pointer to a pointer to a pointer to T, then don't be afraid to write T ***p;. Don't hide pointers behind typedefs unlesswhoever is using the type doesn't have to worry about its "pointer-ness". For example, if you're providing the type as a "handle" that gets passed around in an API, such as:
多重间接不是坏风格,也不是黑魔法,如果你正在处理高维数据,那么你将处理高层次的间接;如果您真的要处理指向指向的指针的指针T,那么不要害怕写T ***p;. 不要在 typedef 后面隐藏指针,除非使用该类型的人不必担心它的“指针性”。例如,如果您将类型作为在 API 中传递的“句柄”提供,例如:
typedef ... *Handle;
Handle h = NewHandle();
DoSomethingWith( h, some_data );
DoSomethingElseWith( h, more_data );
ReleaseHandle( h );
then sure, typedefaway. But if his ever meant to be dereferenced, such as
那么肯定,typedef离开。但是如果h曾经意味着被取消引用,例如
printf( "Handle value is %d\n", *h );
then don't typedefit. If your user has to knowthat his a pointer to int1in order to use it properly, then that information should notbe hidden behind a typedef.
那就不要typedef了。如果您的用户已经知道这h是一个指向int1,以正确地使用它,然后将这些信息应该不被一个typedef背后隐藏的。
I will say that in my experience I haven't had to deal with higher levels of indirection; triple indirection has been the highest, and I haven't had to use it more than a couple of times. If you regularly find yourself dealing with >3-dimensional data, then you'll see high levels of indirection, but if you understand how pointer expressions and indirection workit shouldn't be an issue.
我会说,根据我的经验,我不必处理更高级别的间接性;三重间接性是最高的,我用它的次数不超过几次。如果您经常发现自己在处理 > 3 维数据,那么您会看到高层次的间接性,但如果您了解指针表达式和间接性的工作原理,这应该不是问题。
1. Or a pointer to pointer to
int, or pointer to pointer to pointer to pointer to struct grdlphmp, or whatever.1. 或指向 的指针int,或指向指向 的指针的指针struct grdlphmp,或其他。回答by wilsonmichaelpatrick
After two levels of indirection, comprehension becomes difficult. Moreover if the reason you're passing these triple (or more) pointers into your methods is so that they can re-allocate and re-set some pointed-to memory, that gets away from the concept of methods as "functions" that just return values and don't affect state. This also negatively affects comprehension and maintainability beyond some point.
经过两个层次的间接,理解变得困难。此外,如果您将这些三重(或更多)指针传递到您的方法中的原因是为了让它们可以重新分配和重新设置一些指向的内存,那么就脱离了方法作为“函数”的概念,只是返回值并且不影响状态。这也会对理解和可维护性产生负面影响。
But more fundamentally, you've hit upon one of the main stylistic objections to the triple pointer right here:
但更根本的是,您在这里遇到了对三重指针的主要风格反对意见之一:
One can clearly see how a triple pointer (and beyond, really) would be required.
人们可以清楚地看到如何需要三重指针(甚至更多)。
It's the "and beyond" that is the issue here: once you get to three levels, where do you stop? Surely it's possibleto have an aribitrary number of levels of indirection. But it's better to just have a customary limit someplace where comprehensibility is still good but flexibility is adequate. Two's a good number. "Three star programming", as it's sometimes called, is controversial at best; it's either brilliant, or a headache for those who need to maintain the code later.
这里的问题是“和超越”:一旦你达到三个层次,你在哪里停下来?当然,可以有任意数量的间接层。但是最好在可理解性仍然很好但灵活性足够的地方有一个习惯性的限制。两个是个好数字。“三星级编程”,有时被称为,充其量是有争议的。对于那些需要稍后维护代码的人来说,它要么很出色,要么很头疼。
回答by haccks
Unfortunately you misunderstood the concept of pointer and arrays in C. Remember that arrays are not pointers.
不幸的是,您误解了 C 中指针和数组的概念。请记住,数组不是指针。
Starting from the basics, the single pointer has two purposes: to create an array, and to allow a function to change its contents (pass by reference):
从基础开始,单指针有两个目的:创建数组,以及允许函数更改其内容(通过引用传递):
When you declare a pointer, then you need to initialize it before using it in the program. It can be done either by passing address of a variable to it or by dynamic memory allocation.
In latter, pointer can be used as indexed arrays (but it is not an array).
当你声明一个指针时,那么你需要在程序中使用它之前对其进行初始化。它可以通过将变量的地址传递给它或通过动态内存分配来完成。
在后者中,指针可以用作索引数组(但它不是数组)。
The double pointer can be a 2D array (or array of arrays, since each "column" or "row" need not be the same length). I personally like to use it when I need to pass a 1D array:
双指针可以是一个二维数组(或数组数组,因为每个“列”或“行”不需要相同的长度)。我个人喜欢在需要传递一维数组时使用它:
Again wrong. Arrays are not pointers and vice-versa. A pointer to pointer is not the 2D array.
I would suggest you to read the c-faq section 6. Arrays and Pointers.
又错了。数组不是指针,反之亦然。指向指针的指针不是二维数组。
我建议您阅读c-faq 部分 6. Arrays and Pointers。

