【问题标题】:Implement generic swap macro in C [duplicate]在C中实现通用交换宏[重复]
【发布时间】:2011-04-28 07:06:23
【问题描述】:

可能重复:
is there an equivalent of std::swap() in c

大家好,

我试图在 C 中编写通用交换宏时遇到问题,我的宏如下所示:

#define swap(x,y) { x = x + y; y = x - y; x = x - y; }

它适用于整数和浮点数,但我不确定其中是否有任何问题。如果通用宏是指交换指针、字符等怎么办?谁能帮我写一个通用宏来交换每个输入?

谢谢

【问题讨论】:

  • 由于整数溢出问题,它无法工作。 Paul R 的答案看起来像你想要的。
  • 这不适用于所有花车。想象一下float x = 1e9; float y = 1.0f;。此外,没有为指针定义加法。
  • 为什么不只是带有临时变量的普通代码呢?我很确定临时变量代码即使不是更快也至少是一样快。
  • @CodeInChaos:因为它是一个宏而不是一个实际的模板,所以他不知道该临时变量是什么类型。
  • 虽然这可能确实与stackoverflow.com/questions/2637856/… 密切相关,但我个人认为它不符合重复的条件 - 我已投票重新开放,尤其是到目前为止的答案和讨论非常有见地.

标签: c function macros generics


【解决方案1】:

这仅适用于整数。

对于浮点数,它将失败(例如,尝试用一个非常大的浮点数和一个非常小的浮点数运行它)。

我建议如下:

#define swap(x,y) do \ 
   { unsigned char swap_temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1]; \
     memcpy(swap_temp,&y,sizeof(x)); \
     memcpy(&y,&x,       sizeof(x)); \
     memcpy(&x,swap_temp,sizeof(x)); \
    } while(0)

当在编译时知道要复制的数量时,memcpy 得到了很好的优化。 此外,无需手动传递类型名称或使用编译器特定的扩展。

【讨论】:

  • +1 是唯一正确的答案,而不需要将类型作为参数传递给宏。
  • 为什么不这样做swap_temp[sizeof(x) == sizeof(y) ? sizeof(x) : -1]。您将获得一个通用缓冲区并在编译时检查它们是否相同。
  • 如果我的变量名是swap_temp,这将静默失败。
  • @GMan,是的,这是我亲眼目睹的真实场景(不是专门针对交换,而是针对其他最终导致冲突的宏)。开发人员 A:使用您指定的名称编写解决方案。开发人员 B:看到开发人员 A 解决方案并说“哇,很棒的临时名称”并重新使用它。开发人员 C 将 B 的宏与 A 的宏结合使用。繁荣。
  • 问题不是“如何实现交换宏”,而是“如何在 C 中实现交换宏”。在 C++ 中,您不实现交换宏。就是这么简单。这个问题与 C++ 无关或无关。谈论 C/C++,尤其是在这种特定的上下文中,当 C++ 对同一问题采取完全不同的方法时,这是非常错误的。
【解决方案2】:

你可以这样做:

#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)

然后你会像这样调用它:

SWAP(a, b, int);

或:

SWAP(x, y, float);

如果您乐于使用 gcc 特定的扩展,那么您可以像这样改进:

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

然后就是:

SWAP(a, b);

或:

SWAP(x, y);

这适用于大多数类型,包括指针。

这是一个测试程序:

#include <stdio.h>

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

int main(void)
{
    int a = 1, b = 2;
    float x = 1.0f, y = 2.0f;
    int *pa = &a;
    int *pb = &b;

    printf("BEFORE:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    SWAP(a, b);     // swap ints
    SWAP(x, y);     // swap floats
    SWAP(pa, pb);   // swap pointers

    printf("AFTER:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    return 0;
}

【讨论】:

  • 快速、清晰、正确。 +1
  • 我很好奇;为什么do{}while(0) 构造?解决宏扩展问题?
  • @You:它是宏的规范形式,用于解决 if/else 构造中的无效; 问题。如果去掉do/while (0),那么if (foo) SWAP(x, y); else SWAP(a, b); 之类的表达式会产生错误,因为它会扩展为if (foo) { ... }; else { ... };,而紧接在else 之前的}; 会产生编译错误。
  • @Paul R,调用变量SWAP,令人困惑但有效:)
  • @ideasman42: 是的,虽然这个 kludge 试图解决的问题并没有太多的阴影 - 问题是如果调用参数之一说“temp”并且本地临时变量是也称为“temp”,然后它会中断,因此需要使用尽可能接近唯一的名​​称。 (当然,这甚至可能发生在一个名为“SWAP”的临时变量上,但在这种情况下,任何人都不太可能拥有一个名为“SWAP”的变量。)
【解决方案3】:

GMan 开始了这一尝试,结合inline 函数和宏对其进行编码。此解决方案假设您拥有支持 C99 的现代 C 编译器,因为它使用 compound literal:

inline void swap_detail(void* p1, void* p2, void* tmp, size_t pSize)
{
   memcpy(tmp, p1, pSize);
   memcpy(p1, p2, pSize);
   memcpy(p2 , tmp, pSize);
}
#define SWAP(a, b) swap_detail(&(a), &(b), (char[(sizeof(a) == sizeof(b)) ? (ptrdiff_t)sizeof(a) : -1]){0}, sizeof(a))

这具有以下属性:

  • 它只评估ab 一次。
  • 它有一个编译时检查 尺寸正确。
  • 隐藏的没有命名问题 变量。
  • 临时变量的大小是 在编译时计算,所以 复合文字不是动态的 数组。

需要演员(ptrdiff_t),这样-1 不会被静默提升为SIZE_MAX

这个解决方案仍然有两个缺点:

  1. 它不是类型安全的。它只检查 对于类型的大小,而不是 他们的语义。如果类型 不同,比如大小为 8 的 doubleuint64_t,你有麻烦了。

  2. 表达式必须允许&amp; 运算符适用。因此 它不适用于使用 register 声明的变量 存储类。

【讨论】:

  • 我喜欢它的简洁性,我不知道你可以用数组做到这一点。
  • sizeof *(1 ? &amp;(a) : &amp;(b)) 将是 swap_detail 的第三个参数的更简单且类型更安全的选项。 Credit here
【解决方案4】:

简单地说:您不能在 C 中创建 通用 交换宏,至少,并非没有风险或头痛。 (见其他帖子。解释如下。)

宏很好,但实际代码会遇到的问题是数据类型问题(如您所说)。此外,宏在某种程度上是“愚蠢的”。例如:

使用您的示例宏 #define swap(x,y) { x = x + y; y = x - y; x = x - y; }swap(++x, y) 变成 { ++x = ++x + y; y = ++x - y; ++x = ++x - y;}

如果你运行int x = 0, y = 0; swap(++x, y);,你会得到x=2, y=3,而不是x=0, y=0。除此之外,如果您的宏中的任何临时变量出现在您的代码中,您可能会遇到一些烦人的错误。

您正在寻找的功能是在 C++ 中作为模板引入的。您可以在 C 中获得最接近的方法是为每种可以想象的数据类型或相当复杂的宏使用内联函数(请参阅以前的宏问题和以前的帖子)。

在 C++ 中使用模板的解决方案如下所示:

template<typename T>
inline void swap(T &x, T &y)
{
    T tmp = x;
    x = y; y = tmp;
}

在 C 中,你需要类似的东西:

inline void swap_int(int *x, int *y) { /* Code */ }
inline void swap_char(char *x, char *y) { /* Code */ }
// etc.

或(如多次提到的)一个相当复杂的宏,它可能是危险的。

【讨论】:

  • +1 表示“并非没有风险或头痛”
  • 不,最后你所说的 C 解决方案不是一个,引用不是 C 的一部分。
  • 哎呀!感谢您指出了这一点。意味着那些是指针。会调整它。
  • C 解决方案仍然不是 C。您不能对定义不同参数的函数进行多个定义。可能是swap_int()swap_char() 等?
  • 伙计,他们真的没有在学校教我们这种东西。感谢您的提示。
【解决方案5】:

根据标准,这不一定适用于int。想象一下xy 分别是INT_MAXINT_MAX-1 的情况。第一个加法语句将导致标准未定义的有符号溢出。

int 的交换宏更可靠的实现是 XOR 交换算法

【讨论】:

  • -1 纯粹是基于 XOR 交换算法永远不是正确答案...
  • @Oli,这实际上是一个糟糕的投票理由。这是对交换整数问题的完全有效的答案。
  • @Oli:还是超慢汇编。在现代 CPU 上使用临时的更快。
  • @GMan, @Oli 其他答案中提供的临时解决方案存在缺陷。在临时名称与宏中决定的名称相同的情况下,它们都会静默失败。
  • @Paul,新代码避免了这个问题,但只有在使用变量时才有效。表达式n 不可能产生值(例如指针取消引用)。好处是它会强制编译错误,而不是静默编译和失败。
【解决方案6】:

说真的,您必须在代码中进行多少次交换才能让在给定解决方案的此线程中出现的所有令人头疼的问题都值得?我的意思是,这不是一个 10 行复杂且容易出错的代码结构,它是一个众所周知的习语,只有一个临时变量和三个简单的赋值。把它写在你需要的地方,如果你想节省空间,即使在一行中:

function foo ()
{
    int a, b, tmp;
    ...
    tmp = a; a = b; b = tmp;
    ....
}

或者在 a 和 b 更复杂的地方使用“本地”宏。

#define SWAP(t,a,b) ( (t) = (a), (a) = (b), (b) = (t) )

function foo (pointer)
{
    int tmp;
    ...
    SWAP(tmp, pointer->structure.array[count+1], pointer->structure.array[count+2]);
    ...
}

#undef SWAP

【讨论】:

  • @Secure:确保您的片段完美地展示了危险。首先,它只假设所讨论的三种类型是分配兼容的。如果类型意外不同,您很容易丢失信息。对于您的第一个片段,您至少应该使用 C99 并在您使用它的地方声明 tmp。这至少会记录您期望的类型。第二个片段更糟糕,因为它甚至不允许在同一个地方声明。它显示了这是多么容易出错。
  • @Jens:当您使用参数时,请尝试双方。如果在我在这里使用的本地范围内分配兼容性是一个问题,那么在其他解决方案中使用的全局范围内它会变得多么关键危险?除了 sizeof 之外,它们都没有对 x 和 y 进行类型兼容性检查,据我所知,在没有警告的情况下将指针与 int 交换。包括您的解决方案。
  • @Secure:你是对的,所有这些解决方案都会受到大小相同但语义不同的类型的影响。但至少他们会检查尺寸兼容性,而您的则不会。我想到的问题是一个非常简单的问题,例如asize_tbint。这可能会持续数年,直到您遇到错误的情况。我会在我的解决方案中添加一个备注。
  • @Jens:嗯,这是三个简单的直接赋值a = b;,没有涉及类型转换的复杂计算以及所有相关内容。编写错误只是发生,但严重的是,如果您不能定期正确地 this,那么您将无法编写超出 Hello World 复杂性的任何工作程序,恕我直言。您在这里谈论的是一般问题,而不是特定于交换。你将如何保证函数参数的类型正确性?完全相同的问题,但比直接分配要隐藏得多。
  • @Secure:我个人认为函数调用更具可读性。传递错误类型的参数通常会清楚地显示在调试器中,例如但似乎我们只是在这一点上不同意。就这样吧。
猜你喜欢
  • 2013-08-26
  • 2016-06-01
  • 2017-01-21
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 2018-03-29
  • 1970-01-01
  • 2018-10-14
相关资源
最近更新 更多