【问题标题】:How can I understand the output of this program?我如何理解这个程序的输出?
【发布时间】:2016-05-26 18:48:39
【问题描述】:

我的书试图让我熟悉一些概念,例如关于结构的指针取消引用和一些访问结构的奇怪方法。我是新手,发现下面的代码令人困惑。

#include <stdio.h>
#include <time.h>
void dump_time_struct_bytes(struct tm *time_ptr, int size) {
    int i;
    unsigned char *raw_ptr;

    printf("bytes of struct located at 0x%08x\n", time_ptr);
    raw_ptr = (unsigned char *)time_ptr;
    for (i = 0; i < size; i++)
    {
        printf("%02x ", raw_ptr[i]);
        if (i % 16 == 15) // Print a newline every 16 bytes.
            printf("\n");
    }
    printf("\n");
}
int main() {

    long int seconds_since_epoch;
    struct tm current_time, *time_ptr;
    int hour, minute, second, i, *int_ptr;

    seconds_since_epoch = time(0); // Pass time a null pointer as argument.
    printf("time() - seconds since epoch: %ld\n", seconds_since_epoch);
    time_ptr = &current_time; // Set time_ptr to the address of
                              // the current_time struct.
    localtime_r(&seconds_since_epoch, time_ptr);

    // Three different ways to access struct elements:
    hour = current_time.tm_hour; // Direct access
    minute = time_ptr->tm_min; // Access via pointer
    second = *((int *)time_ptr); // Hacky pointer access
    printf("Current time is: %02d:%02d:%02d\n", hour, minute, second);
    dump_time_struct_bytes(time_ptr, sizeof(struct tm));

    minute = hour = 0; // Clear out minute and hour.

    int_ptr = (int *)time_ptr;
    for (i = 0; i < 3; i++) {
        printf("int_ptr @ 0x%08x : %d\n", int_ptr, *int_ptr);
        int_ptr++; // Adding 1 to int_ptr adds 4 to the address,
    } // since an int is 4 bytes in size.
}

输出:

time() - seconds since epoch: 1189311744
Current time is: 04:22:24
bytes of struct located at 0xbffff7f0
18 00 00 00 16 00 00 00 04 00 00 00 09 00 00 00
08 00 00 00 6b 00 00 00 00 00 00 00 fb 00 00 00
00 00 00 00 00 00 00 00 28 a0 04 08
int_ptr @ 0xbffff7f0 : 24
int_ptr @ 0xbffff7f4 : 22
int_ptr @ 0xbffff7f8 : 4
  1. 我。我知道作者已将 *time_ptr 重新声明为指向 unsigned char 的指针,但它是如何成为数组(我认为是字符数组)的?我认为这可能与数组被解释为指向它们的第 0 个元素的指针有关,但我不确定。

    二。其次,dump_time_struct_bytes 函数的输出(转储的字节)是什么?我知道那是结构中的字节,但我不知道它们应该如何构成存储在其中的 4 小时、22 分钟和 24 秒(如果是这样的话)。还有,*time_ptr的地址对应什么?它是结构的开始吗?如果后者为真,那么输出中对应的转储字节是只属于其第一个元素(tm_sec)还是属于整个结构?

  2. 对“hacky 指针”的解释有点奇怪 - 为什么取消引用转换后的整数指针只会显示结构中第一个元素的内容 - tm_sec?

提前谢谢你。

【问题讨论】:

  • ii.在不提醒自己struct tm 成员的情况下,我可以从printf("%02x ", raw_ptr[i]); 制作的十六进制转储中看到,前 12 个字节是三个 32 位整数,小端格式,因为 hex 18 = dec 24(秒),hex 16 = 12 月 22 日(分钟),4 是小时。
  • 感谢您的回答。然而,有一件事似乎有点不清楚。假设我有一个指向 char 'c' 的指针。指针现在是指向大小为 1 的 char 数组的指针吗?此外,为了验证,您已经提到指向单个结构的指针是指向大小为 1 的数组的指针。因此,将该指针类型转换为指向 char 的指针将导致数组大小现在大于 1 并且是字节数组,负责十六进制转储。因此,任何指针在以这种方式进行类型转换时是否会导致这种良好的字节分解?
  • 你可以索引一个指针就像它是一个数组。 C 在这里没有限制。即使您定义了一个指向 single char 变量的指针,您也可以完全按照您的意愿进行索引,以防止读/写被禁止的内存,或者在写入不'不会导致 direct 错误,但会在一段时间后产生问题 ;-) (这就是为什么指针错误很难追踪的原因)。在这里,char* 指针是从struct 地址转换而来的,因此可以逐字节检查其内容。

标签: c arrays pointers struct


【解决方案1】:

“我知道作者重新声明了 *time_ptr 为指向 unsigned char 的指针,但是它是如何变成一个数组(我认为是字符数组)?”

指针指向内存。内存是一个字节数组。指针指向的字节数取决于所指向事物的解释(类型)。除了这个简单的事实之外,编译器不会在 C/C++ 中进行边界检查。所以本质上每个指针都是指向指针指向的类型的元素数组的指针。所以指向 unsigned char 的指针是指向单字节字符数组的指针。指向结构的指针是指向元素数组的指针,每个元素的长度与一个结构的大小一样长。

因此,指向单个结构的指针IS 指向大小为 1 的数组的指针。语言中没有任何内容可以防止代码出错并尝试访问下一个位置的元素。

这既是指针的力量也是诅咒。以及 C/C++ 中许多错误和安全问题的根源。这也是为什么你可以有效地用语言做很多很酷的事情的原因。

“权力越大,责任越大。”

因此,此代码首先将结构指针解释为字节数组并打印十六进制转储,然后打印为整数数组。将指针作为 int* 处理时,单次递增操作移动 4 个字节。

因此第一个元素是 0x00000018(4 个字节的小端序:18 00 00 00)。 0x18 十六进制是 24。

第二个整数是 0x00000016(16 00 00 00 的小端序)= 22。

等等

请注意,int* 移动了 4 个字节,因为在您的特定编译器中,sizeof(int) == 4。 “int”是一种特殊类型,可以根据您的编译器更改大小。如果您有不同的编译器(例如嵌入式微控制器),则 sizeof(int) 可能为 2,整数将打印为 24、0、22(假设完全相同的内存块)。

Is the size of C "int" 2 bytes or 4 bytes?

===回应评论===

“(不小心在其他地方评论)谢谢你的回答。但是,有一点似乎有点不清楚。假设我有一个指向 char 'c' 的指针。指针现在是指向 char 的指针吗大小为 1 的数组?

是的。一个字节的字节数组。

另外,为了验证一下,您已经提到指向单个结构的指针是指向大小为 1 的数组的指针。

是的,但在这种情况下,数组中单个元素的大小是sizeof(mystruct),这可能不止一个字节。

因此,将该指针类型转换为指向 char 的指针将导致数组大小现在大于 1 并且是一个字节数组,负责十六进制转储。

是的。

因此,以这种方式进行类型转换的任何指针都应该导致这种漂亮的字节分解吗?

是的。这就是字节/内存转储的工作方式。

关于sizeof(type) 关键字的另一件事。 sizeof(type) 报告type 实例的大小(以字节为单位)。 sizeof(variable) 等价于 sizeof(type-of-variable)。当变量是指针或数组时,这有一个微妙的行为。例如:

char c = '0'   // in memory this is the single byte 0x30
char str[] = { 0x31, 0x32, 0x00 }; // an array of bytes 0x31, 0x32, 0x00

sizeof(char) == sizeof(c) == 1
sizeof(str) == 3 // compiler knows the array was initialized to 3 bytes
sizeof(p) == 4 // assuming your compiler is using 32-bit pointers.  On a 64-bit machine this would be 8.

char* p = &c;  //  note that assigning a pointer to the address of a variable requires the address-of operator (&)

sizeof(*p) == 1 // this is the size of the thing pointed to.

p = str; // note that assigning an ARRAY variable name to a pointer does not require address-of (because the name of an array IS a pointer - they *are* the same type in all ways except with respect to sizeof() where sizeof() knows the size of an initialized array.)

sizeof (*p) == 1; // even though p was assigned to str - an array - sizeof still returns the answer based on the type of the thing p is pointing to - in this case a single char.  This is subtle but important.  p points to a single character in the array.

// Thus at this point, p points to 0x31.
p++; // p advances in memory by sizeof(*p), now points at 0x32.
p++; // p advances in memory by sizeof(*p), now points at 0x00.
p++; // p advances in memory by sizeof(*p), now points BEYOND THE ARRAY.

重要 - 因为指针已经超过了数组的末尾,此时 p 指向可能无效的内存,或者它可能指向内存中的其他一些随机变量。如果它指向未按预期使用的“有效”内存,这可能会导致崩溃(在无效内存的情况下)或错误和内存损坏(以及可能的安全错误)。在这种假设变量存在于堆栈中的特定情况下,它指向一个变量或者可能是函数的返回地址。无论哪种方式,超越阵列都是不好的。非常非常糟糕。而且编译器不会阻止你!!!

另外,顺便说一下 - sizeof 不是一个函数。它由编译器在编译时根据编译器的符号表进行评估。因此没有办法得到这样分配的数组的大小:

char* p = malloc(sizeof(char)*100);

编译器没有意识到您分配了 100 个字节,因为 malloc 是一个运行时函数。 (实际上,100 通常是一个值变化的变量)。因此sizeof(p) 将返回一个指针的大小(前面提到的 4 或 8),sizeof(*p) 将返回 sizeof(char),即 1。在这种情况下,代码必须记住分配了多少内存一个单独的变量(或以其他方式 - 动态分配完全是一个单独的主题)。

换句话说,sizeof() 仅适用于类型和静态初始化数组(在代码中初始化的数组),例如:

char one[] = { 'a' };
char two[] = "b";  // using the string quotes results in a final zero-byte being automatically added.  So this is an array of 2 bytes.
char three[3] = "c"; // the specified size overrides the string size, so this produces an array of 'c', 0, <uninitialized>
char bad[1] = "d"; // trying to put 2 bytes in a 1 byte-bag. This should generate a compiler error.

【讨论】:

  • (不小心在别处评论)谢谢你的回答。然而,有一件事似乎有点不清楚。假设我有一个指向 char 'c' 的指针。指针现在是指向大小为 1 的 char 数组的指针吗?此外,为了验证,您已经提到指向单个结构的指针是指向大小为 1 的数组的指针。因此,将该指针类型转换为指向 char 的指针将导致数组大小现在大于 1 并且是字节数组,负责十六进制转储。因此,以这种方式进行类型转换的任何指针都应该导致这种漂亮的字节分解吗?
  • 见上面的额外解释。
  • char bad[1] = "d"; 在 C 中是合法的,它将第一个元素初始化为 'd'
  • char three[3] = "c"; 产生{ 'c', 0, 0 }(未初始化)。见 C11 6.7.9/14 和 /19
  • @M.M - char bad[1]="d" 将在 GCC 中生成警告:error: initializer-string for array of chars is too long [-fpermissive]。忽略警告是一个糟糕的主意,不管是不是合法的“C”。
【解决方案2】:
unsigned char *raw_ptr;
raw_ptr = (unsigned char *)time_ptr;

这将创建一个 unsigned char 类型的指针,并使用指向 struct tm 指针的指针进行初始化(通过强制转换完成)​​。

但是它是怎么变成数组的(我认为是字符数组)

time_ptr 没有改变。程序被告知查看与time_ptr 相同的内存位置,但将其视为unsigned char 类型的数组。

我认为这可能与数组被解释为指向第 0 个元素的指针有关,但我不确定。

数组类型衰减为指针。所以是的,数组由指针表示。但是,指针不一定要与第 0 个索引关联,但在第一次创建数组时就是这样。

其次,dump_time_struct_bytes 函数的输出(转储的字节)是什么?

是的。没有byte 类型,所以经常使用charunsigned char

还有,*time_ptr的地址对应什么?是结构的开始吗?

是的。

如果后者为真,那么输出中对应的转储字节是只属于其第一个元素(tm_sec)还是属于整个结构?

整个结构,因为第二个参数size 是使用sizeof(struct tm) 初始化的(即构成该类型的所有字节)。

“hacky 指针”的解释有点奇怪——为什么解引用转换后的整数指针只会显示结构中第一个元素的内容——tm_sec?

It seems that the first data member is tm_sec and it is of type int。因此,指向struct tm 的指针指向用于存储tm_sec 的同一内存。因此,内存位置被转换为int*,因为tm_sec 的类型为int,并且我们正在处理指向它的指针。然后取消引用以查看该地址的值(当它被视为int 而不是struct tm 时)。


注意:给定任意 4 个字节。是什么意思?如果将它们视为无符号 32 位整数类型,则会生成某个值。如果它们被视为 32 位浮点类型,则可能会产生不同的值。强制转换是强制字节的特定“视图”的方式,而不管字节真正代表什么。

【讨论】:

    【解决方案3】:

    指针struct tm *time_ptr 被类型转换为char *,这仅仅意味着它指向的内存现在将被视为1 字节数据序列。这是用于指针算法的主要概念,指针的类型决定了指针在递增时将移动多少字节。由于这是一个char 指针,递增它只会将其向前移动一个字节,您可以看到内存转储正在逐字节打印。

    在第二种情况下,指针的类型是(int*),指向同一个内存位置,现在将内存视为sizeof(int) 的序列(取决于平台,大小可能会有所不同)。在这种情况下,它是 4 个字节。现在您可以看到 4 字节组 0x00 00 00 18 等于 24 十进制。类似地,0x00 00 00 16 等于十进制的 22,0x00 00 00 04 等于十进制的 4。 (此处考虑字节序)。

    【讨论】:

      猜你喜欢
      • 2011-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-12
      相关资源
      最近更新 更多