一些C指针问题

时间:2017-02-04 23:31:57

标签: c pointers

int *x;        Declares pointer which holds an ADDRESS to a location in memory
*x = 51;       * is de-reference operator. I'm saving 51 at that address.
&x;            & is address of operator, so &x would probably print some memory location

我不明白:

  1. 声明x = 51int *x;会发生什么事情只会在指针中存储'51',所以它指向某个错误“51”位置?

  2. 如果您说&x会怎样?它只是打印指针所在的内存位置吗?

  3. 函数声明为function(int *x),但调用函数的形式为function(&x)。为什么呢?

  4. malloc()我不明白“为什么”它有效,但我明白它的作用。因为在C库定义中它返回一个指向所请求内存的“指针”。

    x = (int *) malloc (sizeof (int)); 将指针放在x ......但x已经是一个指针?它指向另一个指针吗?

    我也看到它写得像x = malloc(sizeof(int)); .....有什么不同?

  5. 5:**运算符,我认为它意味着“指向指针”....但我想我可能不明白何时/为什么会发挥作用?

4 个答案:

答案 0 :(得分:4)

数目:

  1. print("%p", &x);将显示x。

  2. 的地址
  3. 将函数声明为接受指针(例如,存储器地址)。因此,当你调用它时,你必须传递一个指针。 x的地址&是指针。或者您可以声明int z = 10, *y = &z;,然后function(y)来电即可。

  4. x被声明为可以存储指针的内存位置。 malloc()返回它分配的内存地址(指针)。当你说x = malloc(n)时,你指出它应该在x位置存储已分配内存的地址,该位置被声明为存储指针的内存。

  5. **的一个常见用途是当您希望函数保存分配给调用者指针的内存地址时。例如:

  6. // Example of a common use of pointer to pointer (**)
    
    #include <stdio.h>
    #include <stdlib.h>
    
    void mymsg(char **pointer_to_pointer) {
       char *mem = malloc(NBYTES);
       if (mem == NULL) {
           fprintf(stderr, "out of memory\n")
           exit(-1);
       }
       strcpy(mem, "This is some data, also see strdup() man page");
       *pointer_to_pointer = mem;  // Store the pointer mem at the callers pointer 
                                   // storage area, pointed to by 
                                   // pointer_to_pointer
    } 
    
    int main() {
           char *memptr = NULL; // Pointer variable (stores  of chars)
    
           mymsg(&memptr);      // Pass address `*` of our pointer `*` (i.e. **)
                                // mymsg() will store pointer to allocated  
                                // memory in memptr (which is our " holder" 
                                // location).
    
           printf("text: %s\n", memptr);  // Displays text in memory pointed to
                                          // to by memptr. That memory was allocated
                                          // in mymsg(), but that's irrelevant
                                          // now. It doesn't matter where the memory
                                          // was allocated. We have the only
                                          // pointer to it now. mymsg()'s
                                          // variables were all stack local
                                          // and were destroyed when it returned.
    
           free(memptr);        // We can free the memory memptr points to now.
                                // No where else has or needs it at this point.
    
           memptr = NULL;       // Do this so we always know this pointer doesn't
                                // point to valid memory anymore. Because
                                // once the memory is freed contents must be
                                // considered garbage. If you try to free()
                                // memory at that address more than once
                                // it breaks the memory allocator, introducing
                                // nasty difficult to diagnose bug(s).
                                // Most modern C environments make freeing NULL
                                // a NOP (i.e. safe) so you can reduce risk of 
                                // of double-free bugs if you set pointers to null
                                // after freeing them, however, if you keep multiple
                                // pointers to the same memory there's
                                // still risk you might free the same memory 
                                // more than once via another pointer if you're
                                // not careful.
    }
    

答案 1 :(得分:3)

  1. 指针是一个变量,其值是另一个变量的地址。所以,是的,如果你做x = 51,你说地址是51,最有可能破坏内存(除非你确定地址51实际上是正确的)

  2. 是。您可以使用printf("Address of variable: %x\n", &x);查看地址。

  3. 在声明中你说“我期待一个指针”,当你调用它时,你想要传递指针 - 记住,指针只是另一个变量的地址。

  4. 简短的回答是,你根本不需要施展b / c没有任何好处,只有潜在的风险。有关详细信息,请参阅here

  5. 是的,它表示指向指针的指针。这个派上用场的一个例子是一个字符串数组:每个字符串都是一个字符集合,第一个字符串是“第一个”指针。然后你有一个指向这个指针集的辅助指针。

  6. 旁注,看看你是否抓了The C Programming Language的副本,写得很好,我认为这可能会有所帮助。

答案 2 :(得分:2)

  

声明int * x后,x = 51会发生什么?只是一个&#39; 51&#39;存储在指针中,因此它指向一些错误&#34; 51&#34;位置?

它会那样做,是的。如果您尝试取消引用它,则会出现分段错误。

  

如果你说&amp; x会怎么样?它只会打印指针所在的内存位置吗?

  

函数声明是函数(int * x),但调用函数表单是函数(&amp; x)。为什么呢?

使用int *x声明您将内存地址作为参数。使用&x,您将获得x的内存地址,并将其传递给该功能。

  

malloc()我不明白&#34;为什么&#34;它有效,我明白它的作用。因为在C库定义中它返回一个&#34;指针&#34;到所要求的记忆。

我明白为什么你不理解它,你现在的想法有点模糊。

malloc的完整定义是void *malloc(size_t size)。它的工作原理是告诉操作系统为您分配size字节的内存。一旦完成,它将以指针的形式返回分配的内存块的开头的内存地址。这与堆栈分配相反,即堆分配,但我不会详细说明差异。

为了澄清,void*或多或少意味着&#34;指向任何东西&#34;,这意味着您可以将指针强制转换为新的内存块(显式或隐式)以成为指向任何内容的指针数据类型。这意味着您可以将4个已分配的字节视为int,因为例如int(通常)大4个字节。

  

x =(int *)malloc(sizeof(int));将指针放在x ......但x已经是一个指针?它指向另一个指针吗?我也看到它像这样写成x = malloc(sizeof(int)); .....有什么不同?

我已经在上一段中解释了部分内容。

在C ++中,您需要明确地将void*强制转换为int* ,这意味着您需要将(int*)放在前面。但是,C很高兴这个演员隐式地,这意味着它会被放在那里,这意味着两种变体都有效,具体取决于语言。

  

**运算符,我认为它意味着&#34;指向指针的指针&#34; ....但我想也许我不明白何时/为何会发挥作用?

是的,它是指向指针(双指针)的指针。您甚至可以使用***的三重指针。您可以取消引用&#34;多指针&#34;完全使用相同数量的星号(例如**a)。

例如,当您需要在函数内重新分配堆分配的变量时,可以使用它。这是一些代码:

object *a = malloc(sizeof(object));

void reallocate_object(object **b) {
    // Doing *b = x here is the same as doing a = x directly.
    *b = malloc(sizeof(object));
}

reallocate_object(&a);

答案 3 :(得分:2)

  

声明int * x后,x = 51会发生什么?只是一个&#39; 51&#39;存储在指针中,因此它指向一些错误&#34; 51&#34;位置?

基本上,是的。 x是一个与其他变量一样的变量,您可以像其他任何变量一样为其分配新值。表达式51将从int类型转换为int *类型,结果将分配给x

51很可能不是有效的指针值,因此尝试取消引用它可能会导致运行时错误。通过调用&malloc之一,将一元calloc运算符应用于左值 1 ,即可获得有效指针值,或realloc,或在某些情况下使用数组表达 2

  

如果你说&amp; x会怎么样?它只会打印指针所在的内存位置吗?

表达式&x评估到对象x的位置,结果值的类型将为int **(指向指针的指针) int)。

  

函数声明是函数(int * x),但调用函数表单是函数(&amp; x)。为什么呢?

因为,对于x类型的任何对象T表达式 &x会产生T *类型的值:

void foo( T *ptr ) // for any type T
{
  *ptr = new_value(); // write a new value to the object ptr
}                     // points to

void bar( void )
{
  T var;

  foo( &var ); // foo updates the value of var
}

在上面的代码中,

 ptr == &var // T *
*ptr ==  var // T

任何类型都适用,包括指针类型。如果我们用指针类型T替换类型P *,则上面的代码变为

void foo( P **ptr )
{
  *ptr = new_value(); // write a new value to the thing ptr points to
}

void bar( void )
{
  P *var;

  foo( &var ); // write a new value to var
}

此代码与上面的代码完全相同 - 唯一改变的是类型:

  ptr == &var                // P **
 *ptr ==  var                // P *
**ptr == *var == some_object // P
  

malloc()我不明白&#34;为什么&#34;它有效,我明白它的作用。因为在C库定义中它返回一个&#34;指针&#34;到所要求的记忆。

     

x =(int *)malloc(sizeof(int));将指针放在x ......但x已经是一个指针?它指向另一个指针吗?

没有。 x是一个存储指针 value 的对象; malloc是一个返回指针值的函数。

  

我也看到它像这样写成x = malloc(sizeof(int)); .....有什么不同?

(int *)强制转换表达式 - 它告诉编译器将malloc的结果视为指向int的指针。从该语言的1989版本开始,不再需要该演员表,并且不鼓励使用它。 IMO,写malloc电话的正确方法是

T *p = malloc( sizeof *p * number_of_elements );

这是有效的,因为malloc会返回void *,这是一个&#34;泛型&#34;指针类型 - void *值可以转换为任何其他指针类型(反之亦然),而无需强制转换 3

在1989年标准之前,malloc返回char *,它确实需要显式转换以将结果分配给不同的指针类型。

  

5:**操作符,我认为它意味着&#34;指向指针的指针&#34; ....但我想也许我不明白何时/为什么会这样发挥作用?

多个间接(指针指针,指向指针的指针等)是常见的事情。我在上面展示了一个例子,其中一个函数需要为指针类型的参数写一个新值。但是,很难看到超过两三级间接。

修改

一些实际的代码可能有所帮助。这是一个创建两个变量的小例子,一个是int,另一个是int *。我们首先将指针初始化为NULL(一个定义良好的&#34;无处&#34;值,保证比较不等于任何有效指针值),然后我们将其设置为指向另一个整数变量,然后我们将其设置为指向由malloc分配的内存。我已经添加了对我编写的实用程序的调用,以显示每个对象的内容:

#include <stdio.h>
#include <stdlib.h>

#include "dumper.h"

int main( void )
{
  int x = 10;

  /**
   * Start with ptr pointing "nowhere"
   */
  int *ptr = NULL;

  char *names[]  = {"x", "ptr", "unnamed"};
  void *addrs[]  = {&x, &ptr, NULL };
  size_t sizes[] = { sizeof x, sizeof ptr, 0 };

  printf( "ptr is currently NULL\n\n" );

  dumper( names, addrs, sizes, 2, stdout );

  /**
   * Set ptr to point to x
   */
  ptr = &x;  // int * = int *; use the & operator to obtain the location of x

  printf( "ptr currently points to x\n\n" );

  dumper( names, addrs, sizes, 2, stdout );

  ptr = malloc( sizeof *ptr * 10 );    // int * = void *; use malloc to set aside
  if ( ptr )                           // dynamic memory and obtain its location;
  {                                    // in C, you can assign a void * to an int * without a cast
    for ( size_t i = 0; i < 10; i++ )
      ptr[i] = (int) i;
  }

  addrs[2] = ptr;
  sizes[2] = sizeof *ptr * 10;

  printf( "ptr currently points to dynamically allocated memory\n\n" );

  dumper( names, addrs, sizes, 3, stdout );

  free( ptr );

  return 0;
}

在我的系统上构建并运行时(x86-64上的SLES-10,gcc 4.1.2),我得到以下输出:

$ ./pointer_example
ptr is currently NULL

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   00   00   00   00    ....
                0x7fff89aeb2fc   00   00   00   00    ....

ptr currently points to x

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   04   b3   ae   89    ....
                0x7fff89aeb2fc   ff   7f   00   00    ....

ptr currently points to dynamically allocated memory

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   10   20   50   00    ..P.
                0x7fff89aeb2fc   00   00   00   00    ....

        unnamed       0x502010   00   00   00   00    ....
                      0x502014   01   00   00   00    ....
                      0x502018   02   00   00   00    ....
                      0x50201c   03   00   00   00    ....
                      0x502020   04   00   00   00    ....
                      0x502024   05   00   00   00    ....
                      0x502028   06   00   00   00    ....
                      0x50202c   07   00   00   00    ....
                      0x502030   08   00   00   00    ....
                      0x502034   09   00   00   00    ....

在详细介绍之前,请记住x86-64是little-endian,因此从 less -tignificant位开始存储多字节对象。这也意味着跨越多个32位字的对象从最低有效字开始存储。

简而言之,从右到左,从下到上读取每个对象的内存转储。

从第一部分开始:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   00   00   00   00    ....
                0x7fff89aeb2fc   00   00   00   00    ....

对象x从位置0x7fff89aeb304开始存储;在我的系统上,int类型的对象占用4个字节。存储在x的值为100x0000000a;再次,从右到左读取内存转储)。

对象ptr从位置0x7fff89aeb2f8开始存储;在我的系统上,int *类型的对象占用8个字节 4 。存储在ptr的值为NULL(0)。

ptr设置为指向x后,我们会收到以下信息:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   04   b3   ae   89    ....
                0x7fff89aeb2fc   ff   7f   00   00    ....

ptr存储的值现为0x7fff89aeb304,即x的地址。现在,表达式 x*ptr会产生相同的值(10)。

最后,我们使用malloc为10个整数分配空间,并设置ptr指向该序列中的第一个元素:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   10   20   50   00    ..P.
                0x7fff89aeb2fc   00   00   00   00    ....

        unnamed       0x502010   00   00   00   00    ....
                      0x502014   01   00   00   00    ....
                      0x502018   02   00   00   00    ....
                      0x50201c   03   00   00   00    ....
                      0x502020   04   00   00   00    ....
                      0x502024   05   00   00   00    ....
                      0x502028   06   00   00   00    ....
                      0x50202c   07   00   00   00    ....
                      0x502030   08   00   00   00    ....
                      0x502034   09   00   00   00    ....

希望这个模式现在应该是显而易见的 - ptr存储值0x502010,它是动态分配序列的第一个元素的地址。

<小时/>

  1. 左值是一个表达式,它指向一个内存区域,以便可以读取或写入内存。变量是左值,但其他表达式也可以是左值。
  2. 除非它是sizeof或一元&的操作数,或者是用于在声明中初始化字符数组的字符串文字,否则表达式为&# 34; N - T&#34;的元素数组将被转换(&#34;衰减&#34;)到类型为&#34的表达式;指向T&#34;的指针,并且表达式的值将是第一个元素的地址。阵列。
  3. 然而,在C ++中并非如此;在C ++中调用malloc确实需要强制转换,但无论如何你都不应该在C ++代码中使用malloc
  4. 指向不同类型的*可能*具有不同的大小和表示形式,但我认为x86-64对所有指针类型使用8个字节。