【问题标题】:Flow of dereferencing and referencing pointers in CC 中解引用和引用指针的流程
【发布时间】:2018-04-19 19:38:56
【问题描述】:

我正在通过 CPP 学院 CLA 课程材料进行第二次阅读,但我仍在为指针而苦苦挣扎。这是我试图理解的代码 sn-p。我在下面的代码中评论了我的逻辑。

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


int main(void) {
//      we are dereferencing pointer t of type float
//      to = integer literal 1 + float pointer to memory location size 
//      of 2xfloats.
//      I am guessing that this memory location will be filled with 
//      all zeroes or this assignment is invalid.
//      This initial assignment is completely confusing me
//      and rest of my logic/reading this snippet is probably wrong :D

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));
printf("%f\n",*t);      //0.00000

t--;                    //we move  pointer to next zero in memory?!
printf("%f\n",*t);      //0.00000

*t = 8.0;               //we dereference pointer t and assign literal 
                        //8.0. This will be [0] position
printf("%f\n",*t);      //8.00000

t[1] = *t / 4.0;        //we assign index position [1] to (*t (which 
                        //is previously assigned to 8.0) divided by 
                        //4.0 . This will assign t[1] to 2)
printf("%f\n",*t);      //2.00000

t++;                    //we move pointer to next position [1] = 2
printf("%f\n",*t);      //2.00000

t[-1] = *t / 2.0;       //moving to position [0] and assigning it to 1
printf("%f\n",*t);      //2.00000
free(--t);
return 0;
}

抱歉,我没有提到这不是工作计划的一部分。这是一长串简短的“技巧”sn-ps,用于检查对材料的理解。我将仔细阅读您提供的详细答案。谢谢大家:D

【问题讨论】:

  • 这里有什么问题?
  • 您的代码注释“我猜这个内存位置将被全零填充”不正确 - 不要猜,阅读malloc 手册页!
  • 请记住,指针算法会考虑类型。当您将 1 加到一个指针上时,它会增加其类型的大小,在本例中为 sizeof(float)

标签: c pointers memory-management


【解决方案1】:

如果此示例能够代表 CPP 学院提供的课程材料的整体质量,那么我非常强烈建议您在其他地方寻找您的教育和/或认证。

除非此示例的标题为“如何编写使用指针的代码”(在这种情况下它非常有效),否则此代码糟糕。它不仅令人困惑,而且充满了不良做法和未定义的行为。

无端吐槽>

让我们从明显的问题开始:

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));

这 - 我不知道这应该说明什么,除了如何编写真正令人困惑的代码。

sizeof (float) 计算为 float 中的字节数,在大多数现代平台上为 4 字节。对该值进行平方意味着您为N 对象留出足够的空间,其中每个对象的宽度为N 字节宽-IOW,如果sizeof (float) 为4,则您将获得足够的空间容纳4 个对象,而如果sizeof (float) 为8,你有足够的空间容纳 8 个对象。

这是一种... 不寻常 指定您想要多少对象的方式。我们在现实世界中无聊的人只要写sizeof (float) * N,其中N是我们想要的对象的数量(实际上,我们写sizeof *t * N)。

好的,所以我们为 N 个对象分配了足够的空间并返回指针 - 加 1?指针运算考虑了被指向类型的大小,因此给指针加 1 表示“指向被指向类型的下一个对象”(数组下标运算符a[i]定义*(a + i) - 给定起始地址a,计算a之后的i'th 对象的地址并取消引用结果)。

所以基本上,t 开始指向动态缓冲区中的 second 对象。画出来,你会得到这样的东西:

      +---+
      |   |
      +---+
t ->  |   |
      +---+
      |   |
      +---+
      |   |
      +---+

malloc 不初始化动态缓冲区 - 缓冲区的内容是不确定的。无法保证驻留在该内存区域中的位模式对应于值0.00。由于对象未初始化,因此尝试访问其值会导致 未定义行为 - 行

printf("%f\n", *t);

可能导致0.0000的输出,或者它可能导致一些其他随机值,或者您可能会遇到运行时错误,或者...

声明

t--;

从指针中减去 1,使其指向缓冲区中的 first 元素:

      +---+
t ->  |   |
      +---+
      |   |
      +---+
      |   |
      +---+
      |   |
      +---+

*t = 8.0;               //we dereference pointer t and assign literal 
                        //8.0. This will be [0] position
printf("%f\n",*t);      //8.00000

正确,虽然写起来会更清楚一点

t[0] = 8.0;
printf("%f\n", t[0]);

在处理您视为数组的内容时,最好使用数组下标表示法。

t[1] = *t / 4.0;

这段代码很痛。说真的,混合数组和指针表示法肯定会导致胃灼热。最好这样写

t[1] = t[0] / 4.0;

声明

t++;

将指针加 1,使我们回到指向数组第二个元素的先前状态。

t[-1] = *t / 2.0;

这段代码值得一试。负指数是不好的juju。由于t 现在指向数组的第二个元素,这不会爆炸,但它只是......只是...... 不要那样做。如果有人把这样的代码交给我审查,我会狠狠地把它踢回去,他们会感觉一个星期。说真的,不要那样做

以下是代码应该的编写方式:

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

#define N 2 // assuming you only want 2 floats.

int main(void) {
  /**
   * Since the type of t is float *, the *expression* *t has
   * type float - thus, sizeof *t == sizeof (float).  Oh, and
   * the cast on malloc is unnecessary, and under C89 will suppress
   * a useful diagnostic if you forget to include stdlib.h.  
   */
  float *t = malloc( sizeof *t * N ); 

  if ( t ) // ***ALWAYS*** check the result of malloc, calloc, or realloc
  {
    t[0] = 8.0; 
    printf( "%f\n", t[0] );      // 8.000

    t[1] = t[0] / 4.0;
    printf( "%f\n", t[1] );      //2.00000

    t[0] = t[1] / 2.0;         
    printf( "%f\n", t[0]);

    free(t);                     // release the memory
  }
  return 0;
}

请注意,如果您希望将分配的内存初始化为 all-bits-0,则可以使用 calloc 而不是 realloc

float *t = calloc( N, sizeof *t );

请注意,all-bits-0 不一定对应于0.000

【讨论】:

  • 谢谢。我的眼睛在sizeof(float) * sizeof(float) 上大吃一惊。如果sizeof(float)1 会不会很有趣?
  • @JohnKugelman:那条线上的 1 + 让我着迷——当我看到它并理解他们在做什么时,我差点被咖啡呛到。
  • 感谢您的解释,这有助于并消除我对分配内存作用的错误理解。再一次提到,这个“技巧” sn-p 来自一长串 sn-ps,它正在测试您对材料的理解。所有这些都非常奇怪,让我哭泣。但我会记住在现实世界中不该做什么:D
  • @Peja:是的,我只是讨厌这类例子。有一些方法可以测试您的知识,而无需求助于与实际代码无关的“棘手”代码。并不是没有一些糟糕的真实世界的代码,只是以不同的方式糟糕。
【解决方案2】:

您的几乎所有假设似乎都是正确的; 只有第一个“我猜这个内存位置将被全零填充或者这个分配无效”可能更具体一点:

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));

为多个浮点值分配内存;实际上sizeof(float) * sizeof(float) 很少有意义,因为可用的浮点值的数量取决于浮点数的大小。它不会用0初始化内存,这样内存就不会被初始化。而且 - 这是现在的主要内容 - 访问未初始化的值,就像使用 printf("%f\n",*t); 一样会产生未定义的行为。编译器可以做任何事情,甚至完全忽略该语句。

所以你实际上会写

float *t = 1 + (float *) calloc(sizeof(float), 4);

calloc 将内存初始化为0,浮点数的确定性更高一些。

【讨论】:

  • 理论上calloc 是不够的,因为系统可能不遵循 IEC 60559
【解决方案3】:

第一个printf("%f\n",*t); 通过读取未初始化的内存位置并将结果传递给标准库函数来引发undefined behaviour

正如链接中所解释的,这意味着程序的整个输出是没有意义的。超出这一行的代码阅读也没有意义。

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 2015-04-21
    • 1970-01-01
    • 2015-02-13
    • 2014-05-06
    • 2020-08-19
    • 1970-01-01
    • 2020-11-29
    相关资源
    最近更新 更多