【问题标题】:How do you explain the output from this function-like macro `slice` in C?你如何解释 C 中这个类似函数的宏 `slice` 的输出?
【发布时间】:2013-08-31 11:01:07
【问题描述】:
#include <stdio.h>

#define slice(bare_string,start_index) #bare_string+start_index
#define arcane_slice(bare_string,start_index) "ARCANE" #bare_string+start_index

int main(){
    printf("slice(FIRSTA,0)==> `%s`\n",slice(FIRSTA,0));
    printf("slice(SECOND,2)==> `%s`\n",slice(SECOND,2));
    printf("slice(THIRDA,5)==> `%s`\n",slice(THIRDA,5));
    printf("slice(FOURTH,6)==> `%s`\n",slice(FOURTH,6));
    printf("slice(FIFTHA,7)==> `%s`\n",slice(FIFTHA,7));
    printf("arcane_slice(FIRSTA,0)==> `%s`\n",arcane_slice(FIRST,0));
    printf("arcane_slice(SECOND,2)==> `%s`\n",arcane_slice(SECOND,2));
    printf("arcane_slice(THIRDA,5)==> `%s`\n",arcane_slice(THIRDA,5));
    printf("arcane_slice(FOURTH,6)==> `%s`\n",arcane_slice(FOURTH,6));
    printf("arcane_slice(FIFTHA,7)==> `%s`\n",arcane_slice(FIFTHA,7));
    return 0;
}

输出:

slice(FIRSTA,0)==> `FIRSTA`
slice(SECOND,2)==> `COND`
slice(THIRDA,5)==> `A`
slice(FOURTH,6)==> ``
slice(FIFTHA,7)==> `slice(FIFTHA,7)==> `%s`
`
arcane_slice(FIRSTA,0)==> `ARCANEFIRST`
arcane_slice(SECOND,2)==> `CANESECOND`
arcane_slice(THIRDA,5)==> `ETHIRDA`
arcane_slice(FOURTH,6)==> `FOURTH`
arcane_slice(FIFTHA,7)==> `IFTHA`

我有上面需要帮助的 C 代码。我从
得到了奇怪的行为 类似于函数的宏 slice 应该从传递的索引中“切片”
到字符串的末尾。它不是真正意义上的切片,而是通过
从某个点到 printf 的指针,从该点开始打印
地址。我设法弄清楚arcane_slice 中的字符串
首先连接然后“切片”。我也发现当start_index
等于 6 printf 从空字节开始打印,这就是为什么
你得到'空'字符串。奇怪的是start_index 是 7。它打印
printf(interpolator string) 的第一个参数与两者中传递的裸字符串连接。
arcane_slice 和 slice(如输出中的第 5 行和第 10 行所示)
为什么是这样吗?
我最疯狂的猜测是,当start_index 超过字符串的长度时,
指针指向程序地址空间中数据段的开始。但是
那么你可以用“为什么不从FIRSTA开始打印”来反驳这一点

【问题讨论】:

  • 您的“最疯狂的猜测”是正确的。但是,超出为字符数组正确分配的内存会调用未定义的行为,因此任何事情都可能发生。只是不要这样做。

标签: c string macros


【解决方案1】:

不是任何“数据段”,堆栈。这就是我所记得的:当 C 调用一个函数时,它首先将数据放入堆栈,第一个变量参数,然后是格式,所有这些都是按顺序分配给您的文本的内存地址。在那个内存块中,最后一个参数(c-string)在最前面,第一个在最后,因此:

内存:

"FIFTHA\0slice(FIFTHA,7)==> `%s`\n\0"

参数:

<pointer-to-"FIFTHA"> <pointer-to-"slice...">

由于您过度增加第一个字符,它会跳过 '\0' 字符并指向格式。

尝试用更多的占位符来试验这个,比如

 printf("1: %s, 2: %s\n", slice(FIFTHA,7), slice(FIFTHA,6));

【讨论】:

  • 它并不能完全重现相同的输出。它真的是不确定的(如果可以量化不确定性)。我得到空字节和高 ascii 字符。
  • 该死的,我的cmets搞砸了。 Rishta 的 cmets 应该是 Gangadhar 的,反之亦然。我发现 rishta 的回答最有帮助。
【解决方案2】:
 slice(bare_string,start_index) #bare_string+start_index  

您正在传递一个string,bare_string 存储您传递的字符串的起始地址,然后您返回更改后的指针位置,即bare_string+start_index

char str[6]="Hello";

char *ptr =str;

printf("%s\n",str);//prints hello

printf("%s\n",str+1);//prints ello
printf("%s\n",str+2);//prints llo
printf("%s\n",str+3);//prints lo
printf("%s\n",str+4);//prints o
printf("%s %c=%d  \n",str+5,*(str+5),*(str+5));//prints Null
printf("%s %c=%d  \n",str+6,*(str+6),*(str+6));//prints Null or may be Undefined behavior
printf("%s %c=%d  \n",str+7,*(str+7),*(str+7));//prints Null or may be Undefined behaviour

您的情况也发生了同样的情况。

测试代码:

#include<stdio.h>

main()
{
char str[6]="Hello";

char *ptr =str;

printf("%s\n",str);//prints hello

printf("%s\n",str+1);//prints ello
printf("%s\n",str+2);//prints llo
printf("%s\n",str+3);//prints lo
printf("%s\n",str+4);//prints o
printf("%s %c=%d  \n",str+5,*(str+5),*(str+5));//prints Null
printf("%s %c=%d  \n",str+6,*(str+6),*(str+6));//prints Null or may be Undefined behavior
printf("%s %c=%d  \n",str+7,*(str+7),*(str+7));//prints Null or may be Undefined behaviour

}

【讨论】:

  • 这个解释确实有点意思。我不知道第一件事和最后一件事。但是,如果最后一个字符('\n')的地址是 1222222 并且您添加 7(超过字符串长度),如果 char 是一个字节,则指针应该位于 1222229。这肯定不能和 printf("slice(FIFTHA,7)==> %s\n") 中第一个字符('F') 的地址相同。此外,如果您将 start_index 设置为 42 以后,它会开始打印以下 printf 的第一个参数,例如 start_index 的 45 导致 rcane_slice(FIRSTA,0)==&gt; %s`。
  • 就像printf 的所有第一个参数都串成一个长字符串。我玩了一点,它一直持续到start_index 大约是 267。之后,它开始侵占 GCC 使用的内存空间,例如在 272 处打印Mingw runtime failure: 我认为可以肯定地说这种行为是越界时未定义。我一直在寻找任何其他答案,但“行为未定义”。尽管如此,它仍然给我留下了更多的问题。就像,为什么一直没有段错误?为什么输出与@Gangadhar 的输出不同?
  • @Segfault 正如你所说“如果最后一个字符的地址是 1222222 并且你添加 7(超过字符串长度),如果 char 是一个字节,则指针应该在 1222229。”,这个是绝对正确的。你怎么说“这肯定不能与 printf("slice(FIFTHA,7)==> %s\n") 中第一个字符('F') 的地址相同。”我尝试了不同的值,我在 GCC 和 turbo-C 上得到了适当的结果。
  • 对不起@Gangadhar,我的评论有点误导。我并没有真正得到那个错误。程序从恰好有字符串“Mingw 运行时失败”的内存位置读取。几乎每次运行 ite.g 时输出都会发生变化。例如,此时我收到“VirtualQuery failed for %d bytes at address %p”。
【解决方案3】:

您自己已经回答了您的问题。 "FIFTHA"+7 给你一个字符串对象之外的指针,这是 C 中未定义的行为。

没有简单的方法可以在 C 中为此类“切片”获得更类似于 Python 的行为。您可以通过向字符串添加后缀(全为零字节)使其适用于达到某个上限的索引:

#define slice(bare_string,start_index) ((#bare_string "\0\0\0\0\0\0\0")+(start_index))

此外,在使用宏时,过度使用括号是一种很好的做法(也可以避免错误)。

#define slice(bare_string,start_index) ((#bare_string)+(start_index))
#define arcane_slice(bare_string,start_index) (("ARCANE" #bare_string)+(start_index))

【讨论】:

  • 这没有回答问题。
  • @nickie,你读懂了我的想法。我试图想出一种方法来做类似 python 的切片(滥用预处理器很有趣)。不过,额外的大括号似乎并没有改善它。
猜你喜欢
  • 1970-01-01
  • 2015-05-12
  • 2014-10-01
  • 2020-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
  • 1970-01-01
相关资源
最近更新 更多