指针和指向指针的指针问题。

8

我有些困惑,不明白一段代码为什么能够运行。下面是stdlib库实现的qsort函数的比较函数:

int scmp(const void *p1, const void * p2)
{
  char *v1, *v2;

  v1 = (char *) p1;
  v2 = (char *) p2;
  return strcmp(v1,v2);
}

当然,这仅适用于字符串。我的问题是:为什么下面的代码有效?
int scmp(const void *p1, const void * p2)
{
  char *v1, *v2;

  v1 = *(char **) p1;
  v2 = *(char **) p2;
  return strcmp(v1,v2);
}

在第二个版本中,我强制将明显是char*的变量转换为char**。问题在于变量仍然包含一个指向char变量的地址。当我应用*时,我的理解是C会通过获取p1的内容,然后读取存储在内部地址后面的8个字节(在我的架构上),以便最终得到char*类型的值。在我看来,这应该导致将8个字符连接到无效的内存地址中。
尽管如此,两个函数都能正常工作。我错在哪里?

1
第二个版本对我来说是正确的。如果没有看到你代码的其余部分,很难说出第一个版本是如何工作的。 - R Sahu
1
你怎么调用这个函数?如果你像这样调用函数 scmp("hello", "hello"); 只有第一个版本会起作用:http://ideone.com/P96Wmj - mch
1
它在某些情况下可能有效,但并非所有情况都适用。例如,如果p1指向字符串“abcdefgh”,而p2指向另一个字符串“abcdefgh”。现在这两个字符串相等,因此它们都被解释为相同的地址(我们称之为p)。然后strcmp将比较p处的字符串与p处的字符串,由于两个参数都指向相同的地址,因此内容根据定义是相同的。 - P. Kouvarakis
@RSahu:你能解释一下为什么它看起来是正确的吗? - P. Kouvarakis
1
这两个函数都是正确的,取决于 p1p2 是什么,但第二个函数是在比较 char*(字符串)数组时应该用作 qsort() 中的比较函数。 - francis
显示剩余2条评论
1个回答

6

假设您想使用qsortint数组进行排序。

int numbers[] = {10, 50, 35, 62, 22};

首先,您需要创建一个可以比较两个int的函数。

int intCompare(void* p1, void* p2)
{
   int n1 = *(int*)p1;
   int n2 = *(int*)p2;
   return (n1 < n2);
}

那么,您可以使用:

qsort(numbers, 5, sizeof(int), intCompare);

当将numbers传递给qsort时,它会被衰减为int*并作为void*传递。当我们需要从intCompare中的void*提取数字时,我们需要将其强制转换为int*,然后再解除指针引用并比较值。
以字符串为例,如果您想要排序:
char* strings[] = { "abc", "xyz", "def" };

调用 qsort 的代码将是:
qsort(strings, 3, sizeof(char*), scmp);

当将字符串传递给qsort时,它会被降级为char **并作为void *传递。由qsort传递给scmp的指针的基础类型将是char **,而不是char *。因此,使用如下内容是正确的:

int scmp(const void *p1, const void * p2)
{
  char *v1, *v2;

  v1 = *(char **) p1;
  v2 = *(char **) p2;
  return strcmp(v1,v2);
}

第一个版本有时会因为幸运的巧合而正常工作。以下是一个示例程序,展示了一些第一个版本无法正常工作的情况,而第二个版本应该始终能够正常工作。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// First version of scmp
int scmp1(const void *p1, const void * p2)
{
  char *v1, *v2;

  v1 = (char *) p1;
  v2 = (char *) p2;
  return strcmp(v1,v2);
}

// Second version of scmp
int scmp2(const void *p1, const void * p2)
{
  char *v1, *v2;

  v1 = *(char **) p1;
  v2 = *(char **) p2;
  return strcmp(v1,v2);
}

void test1()
{
   char* strings[] = { "abc", "xyz", "def" };
   qsort(strings,  3, sizeof(char*), scmp1);
   for( int i = 0; i < 3; ++i )
   {
      printf("%s\n", strings[i]);
   }
   printf("\n");
}

void test2()
{
   char* strings[] = { "abc", "xyz", "def" };
   qsort(strings,  3, sizeof(char*), scmp2);
   for( int i = 0; i < 3; ++i )
   {
      printf("%s\n", strings[i]);
   }
   printf("\n");
}

void test3()
{
   char** strings = malloc(3*sizeof(char*));
   strings[0] = "abc";
   strings[1] = "xyz";
   strings[2] = "def";

   qsort(strings,  3, sizeof(char*), scmp1);
   for( int i = 0; i < 3; ++i )
   {
      printf("%s\n", strings[i]);
   }

   free(strings);
   printf("\n");
}

void test4()
{
   char** strings = malloc(3*sizeof(char*));
   strings[0] = "abc";
   strings[1] = "xyz";
   strings[2] = "def";

   qsort(strings,  3, sizeof(char*), scmp2);

   for( int i = 0; i < 3; ++i )
   {
      printf("%s\n", strings[i]);
   }

   free(strings);
   printf("\n");
}

int main()
{
   // Does not work.
   test1();

   // Should work always.
   test2();

   // Does not work.
   test3();

   // Should work always.
   test4();
}

输出(使用gcc 4.8.4):

abc
xyz
def

abc
def
xyz

abc
xyz
def

abc
def
xyz

1
很高兴看到一些非常详细的答案。+1 - Mirakurun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接