2

我正在学习 bison/yacc(也正在复习一些 c)并尝试将 json 解析器构建为简单的测试项目。

使用在http://www.json.org/上找到的术语,我有一个表示字符串/值对的结构对和一个表示具有成员字段的对象的结构对象,该成员字段基本上包含指向对的链接列表的指针.

我有一个简单的 c 函数(create_pair),它返回一个新的对。我注意到一个我无法解释的奇怪行为:

  • 如果我从“main”调用这样的函数并打印返回结构的内存地址,它们的地址总是不同的。
  • 如果我在野牛“动作”中调用相同的函数,我会看到我的函数返回一个指针,该指针恰好总是驻留在相同的内存地址上。

这有道理吗?

详细信息/代码如下:

这是代码(链接包含指向“项目”中包含的四个不同文件的 4 个 pastebin 链接的列表):

你可以编译并运行它:

lex t.l
yacc -d t.y
cc y.tab.c lex.yy.c t.c
./a.out

如果您启动代码并使用以下输入运行它:

{“名字”:“A”,“姓氏”:“B”}

你会看到:

1)在“main”中执行的代码(检查文件ty),创建四个不同的对对象,然后我打印它们的内存地址,输出类似于(注意不同的地址):

p 0x7fff52476be8 //(<-memory address for pair p)
print pair: P, Hellov
q 0x7fff52476bc8 //(<-memory address for pair q)
print pair: Q, Hellox

2)只要我粘贴上面的 json 示例,我们就两次点击“pair”规则,第一次是“firstName”:“A”,第二次是“lastName”:“B”,我在两种情况并打印内存地址,它们是相同的:

Creating pair 0x7fff52475c88
print pair: firstName, A
Creating pair 0x7fff52475c88
print pair: lastName, B

为什么会这样?

4

2 回答 2

2

你不应该关心 a 的地址pair是什么。这与与他们一起执行的工作无关,您看到的地址是偶然的,没有任何后果。

您的函数create_pair不返回指针。它是用 声明的pair create_pair(…),因此它pair按值返回 a 。

main中,您定义pair p = create_pair(l, v);. 这会创建一个自动对象p,通常是通过在堆栈上为其留出空间。然后它调用create_pair. 的返回值create_pair被复制到p. 稍后,当您打印时&p,您打印的是 的地址p,而不是create_pair返回的地址。

同样,当您定义 时pair q = create_pair(l, x);,您会创建另一个对象q。因为这个对象的生命周期与 的生命周期重叠p,所以它们必须在不同的地方,所以它们有不同的地址。当您打印时&q,您会看到这个不同的地址。

接下来,考虑您放置在 Bison 规则中的代码,pair p = create_pair($<u_string>1, $<u_value>3);. Bison 在处理规则时执行此代码。它创建一个自动对象,然后打印它的地址。然后执行离开了这段代码的范围,Bison 无疑会继续做其他事情,并且退出它当前正在进行的处理。自动对象的生命周期结束,堆栈上的数据被弹出。后来,Bison 又回到了这条规则。那时,仅仅因为计算机是机械操作的,堆栈指针的地址与以前相同。所以,当一个新p的被创建时,它恰好和旧的在同一个地方p。与pq不同,因为它们同时存在,所以它们必须在不同的地方,这个旧的p和新的p只存在于不同的时间,所以它们可能在同一个地方。

这不一定总是发生。如果你的语法更复杂,Bison 可能一次在堆栈中有其他东西,而不是另一个(或者可能没有;Bison 产生的解析机可能不会那样做;我不知道)。或者,如果您在另一个规则中有相同的代码,则在处理该规则时堆栈可能会有所不同。

于 2012-12-23T00:33:37.427 回答
0

因此,您看到的是堆栈变量的地址发生了变化。这是完全正常的。如果它们都具有相同的地址,您将得到一个值覆盖另一个值,这不会很有用。

编辑:当你调用一个函数(从同一个调用者函数,例如main)时,堆栈变量的地址总是相同的 - 因为在调用时堆栈开始是相同的[通常 - 当然,有时编译器用堆栈做有趣的事情,所以不能 100% 保证]

Edit2:澄清一下,如果调用是相同的调用链,例如从函数 C 从函数 B 调用函数 A,那么无论在 B 或 C 中的哪个位置进行调用,A 中的堆栈通常都是相同的。当然,如果我们从函数 C 从函数 D 调用函数 A,那么关于 A 中局部变量的地址的所有赌注都将失败[嗯,它很可能非常相似,但是如果函数 D 有一些巨大的局部变量,它可能非常不同]。并且这是典型的警告仍然适用。编译器很可能会留下清理堆栈“直到它有足够的麻烦”,而不是清理每个调用,这意味着对函数 A 的三个调用可能会在堆栈上积累一些“垃圾”,这些“垃圾”不会被清理到后来。

我有点困惑,为什么您认为它应该与此不同?

于 2012-12-23T00:26:02.783 回答