当一个参数通过另一个参数的引用传递时,printf如何处理它的参数

4

我发现了一些有趣的东西,但是无法解释。

我正在编写一个简单的程序来反转一个字符串。这个程序运行良好,没有任何问题。

我的问题在于printf函数。当我分别打印原始字符串时,它打印正确,但是当我将原始字符串作为第一个参数,将反转函数调用作为第二个参数进行打印时,两者都显示为反转后的字符串。

输出:

|| abcdthelgko ||
Orig Str: | okglehtdcba |, Rev Str | okglehtdcba |

代码:

char* ReverseStr(char* str, int len)
{
  char *start = &str[0], *end = &str[len-1];
  char temp;

  while(start < end)
  {
    temp = *start;
    *start = *end;
    *end = temp;

    start++;
    end--;
  }
  return str;
}

int main()
{
  char str_unique[] = "abcdthelgko";
  int str_unique_len = sizeof(str_unique)/sizeof(str_unique[0]);

  printf("\n || %s || \n", str_unique);
  printf("Orig Str: | %s |, Rev Str | %s |\n", str_unique, ReverseStr(str_unique, str_unique_len-1));
  return 0;
}

* 反馈后的修改:* 为了测试以下两个理论: 1. Printf从右到左处理注释 2. 如果函数调用是参数之一,则在printf之前调用该函数, 我编写了另一个示例代码,只是更改通过引用传递的变量的值。 我看到的是,在变量值中,它可以正常工作: ================== 输出 ======================

 i = 5, changed i = 2, again i= 2 

 changed i = 2, i= 2 

 i = 5, changed i = 2 

================== CODE ======================

int* change (int* addr);

int main()
{
  int i;
  i=5;
  printf("\n i = %d, changed i = %d, again i= %d \n ", i, *change(&i), i);

  i=5;
  printf("\n changed i = %d, i= %d \n", *change(&i), i);

  i=5;
  printf("\n i = %d, changed i = %d \n", i, *change(&i));
  return 0;
}

int* change (int* addr)
{
  *addr = 2;
  return (addr);
}

提示:尝试使用与第一个printf相同的第三个printf。 - Sourav Ghosh
嗨, 为了检查printf函数是否从右到左处理参数,或者如果函数调用是printf的参数,则在调用printf函数之前先处理该函数调用,我编写了另一个代码。 我尝试了一个简单的实验来改变通过引用传递的变量值,而在这种情况下,我看到了原始输出。i = 5,我将i的地址传递给一个函数,在那个位置写入2: [CODE]:printf("\n i = %d, changed i = %d \n", i, *change(&i));[OUTPUT]:i = 5,changed i = 2 - Sougata Chatterjee
4个回答

6

由于ReverseStr是在原地进行操作的(即所谓的改变输入),因此“原始”字符串不再可用。

当您调用一个带有另一个函数调用作为其参数之一的函数时,内部调用会首先执行。例如:

printf("%lf", pow(2, 2));

在调用printf函数之前,会先调用幂函数(pow)。你的代码也是这样,它会改变原始字符串,所以当调用printf时,它获取的是已经反转的字符串的地址 "在两个参数中"。

解决这个问题的一种方法是分配原始字符串的副本,将其传递给ReverseStr函数,这样你就可以同时拥有两个版本:

int main()
{
  char str_unique[] = "abcdthelgko";
  char *str_dup = strdup(str_unique);
  int str_unique_len = sizeof(str_unique)/sizeof(str_unique[0]);

  printf("\n || %s || \n", str_unique);
  printf("Orig Str: | %s |, Rev Str | %s |\n", str_unique,
      ReverseStr(str_dup, str_unique_len - 1));

  free(str_dup); // Don't forget to free the memory allocated by strdup!
  return 0;
}

你好,在你提到的这一点上: 当你调用一个函数并将另一个函数调用作为其参数之一时,内部调用会首先执行。这似乎不是真的。我尝试了一个简单的实验来改变通过引用传递的变量值...在这种情况下,我看到了原始输出。i = 5,我将i的地址传递给一个函数,在那个位置写入2:[代码:printf("\n i = %d, changed i = %d \n", i, *change(&i));] [输出]:i = 5,changed i = 2 - Sougata Chatterjee
嗨,我更新了问题并进行了另一个实验...似乎不成立! - Sougata Chatterjee
@SougataChatterjee - 你为什么认为这不成立?你的例子再次证明了它的正确性。而且,没有其他的方式,因为在使用函数输出的函数返回后再调用该函数是毫无意义的。如果需要,你也可以使用调试器验证这一点。 - Amit
因为如果是真的,在printf中当我打印i并且改变(&i)时,两者都应该打印出2的更改值。但我看到它打印了5和2。但对于字符串...字符串和func(string)都打印了反转的字符串。明白了吗? - Sougata Chatterjee
@SougataChatterjee - 我只能说它确实是这样工作的,而且在评论(甚至回答)中解释起来太长了。我给你的建议是从基础开始,尽可能全面地学习指针、非指针类型、函数调用和堆栈。我相信一旦你做到了,你对计算机如何工作、编程如何工作以及这个特定问题将会非常明显。 - Amit

0

你可以在ReverseStr中复制传递的字符串并返回它,而不做任何修改,而传递的字符串将被反转。(通常返回一个按引用传递的值是多余的,但通过返回原始副本,你的函数将证明有价值的返回值而不是void)。

或者为反转后的字符串分配内存,并且永远不要改变原始字符串:

char* ReverseStr(char* str, int len) {
    char *reversed = malloc(len * sizeof(char));
    int i, j = len - 1;
    for (i = 0; j >= 0; i++) {
        reversed[i] = str[j--];
    }

    return reversed;
}

返回原始字符串但反转传递的字符串,因此您可能需要在主函数中反转打印语句

char* ReverseStr(char* str, int len) {
    char *start = &str[0], *end = &str[len-1];
    char temp;
    int i;

    char *original = malloc(len * sizeof(char));
    for (i = 0; str[i] != '\0'; i++) {
        original[i] = str[i];
    }

    while (start < end) {
        temp = *start;
        *start = *end;
        *end = temp;

        start++;
        end--;
    }
    return original;
}

  1. 按照问题本身的示例返回地址,绝对不会有冗余。
  2. strdup
- Amit
我想你是对的,但在这种情况下,我的观点是强调传递了一个引用,因此您将在主函数中访问它而无需返回它。在函数调用之后调用该函数或仅访问变量将产生相同的结果。 - Lukas
这是一种风格/便利性决策,对性能影响很小(或者通过正确的优化根本没有影响),但它是一种合法的模式。 - Amit
嗨,我的问题不是“如何获取原始字符串并获取反向字符串?”有很多方法可以做到这一点。我的观点是,当我先打印原始字符串,然后调用反转时,它会修改该字符串......那么为什么原始字符串会被翻转呢?这更多是关于printf如何处理它的问题,而不是一个关于字符串反转的问题! - Sougata Chatterjee
代码返回 original,但由于数组不包含空字符,它并不指向字符串。建议确保 original 指向一个字符串。 - chux - Reinstate Monica
如果您同意在函数中修改原始字符串,那么在调用变异函数后如何期望它打印旧值呢? - Lukas

0

在printf完成之前,它运行了ReverseStr函数,
str_unique中存储的字符串在ReverseStr之后发生了改变
然后一切都改变了:)


你好,在你提到的这一点上:“在printf完成之前运行ReverseStr”,这似乎不是真的。我尝试了一个简单的实验来改变通过引用传递的变量值,而不是使用str reverse...在那种情况下,我看到了原始输出。i是5,我将i的地址传递给一个函数,在那个位置写入2: ___代码___ :: printf("\n i = %d, changed i = %d \n", i, *change(&i)); ___输出___:: i = 5,改变后的i = 2 - Sougata Chatterjee
根据您的反馈,我更新了原始问题,并基于最新实验进行了修改...似乎不能改变数字的值。 - Sougata Chatterjee
你的新实验在VS2015上输出了以下内容:i = 2,更改后 i = 2,再次 i = 5更改后 i = 2,i = 5i = 2,更改后 i = 2 - user2986683

-1

函数ReverseStr()是在原地修改提供的字符串,而不是将其复制到一个新的字符串中。

在C语言中,参数从右到左依次压入堆栈。在调用printf()时,ReverseStr()返回的结果首先被压入(同时原始字符串str_unique也被反转),然后指向str_unique的指针被压入,但其内容已经被ReverseStr()反转。

为了保持原始字符串不被修改,你应该将其复制到一个新的目标位置。这个新的目标位置可以在运行时(使用malloc)在ReverseStr内部分配(不要忘记在使用完之后释放它),或者由调用者提供。


1
在C语言中,参数可以以任何顺序传递,并且没有要求它们必须在堆栈上。虽然有些情况下参数是从右到左推入堆栈中,但这并不是普遍的真理。 - Paul R
你说得对,它们并不总是通过栈传递。然而,当参数通过寄存器传递时,为了明显的兼容性原因,行为完全相同(从右到左执行参数函数)。 - Alejandro López
这取决于ABI - 有些ABI甚至使用寄存器和堆栈参数传递的混合方式。还要注意评估顺序与参数传递顺序不同 - 没有要求以任何特定顺序评估参数。 - Paul R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接