我怎样才能理解这个程序的输出?

时间:2016-05-26 18:48:39

标签: c arrays pointers struct

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

#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个元素的指针的事实有关,但我不确定。

    II。其次,dump_time_struct_bytes函数的输出是什么(转储的字节)?我理解这就是结构中的字节,但我不知道它们应该如何构成存储在其中的4小时22分24秒(如果是这种情况的话)。另外,* time_ptr的地址对应于什么?它是结构的开始吗?如果后者为真,那么输出中相应的转储字节是仅属于其第一个元素(tm_sec)还是属于整个结构?

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

  3. 提前谢谢。

3 个答案:

答案 0 :(得分:3)

“我理解作者已将* time_ptr重新声明为指向unsigned char的指针,但它是如何设法成为一个数组(我认为是字符数组)?”

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

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

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

“强大的力量带来了巨大的责任。”

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

因此第一个元素是0x00000018(4个字节的小端:18 00 00 00)。 0x18 hex是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的数组?

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

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

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

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

YES。

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

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

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

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.

答案 1 :(得分: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)初始化的(即包含该类型的所有字节)。

  

&#34; hacky指针的解释&#34;有点奇怪 - 为什么取消引用转换后的整数指针只能揭示结构中第一个元素的内容-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位浮点类型,则可能产生不同的值。施法是强制特定观看的方式。不管字节真正代表什么,都是字节数。

答案 2 :(得分:1)

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

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