Truyền đối số biến cho một hàm khác chấp nhận danh sách đối số biến


114

Vì vậy, tôi có 2 hàm mà cả hai đều có đối số tương tự

void example(int a, int b, ...);
void exampleB(int b, ...);

Bây giờ examplecác cuộc gọi exampleB, nhưng làm cách nào tôi có thể truyền các biến trong danh sách đối số biến mà không cần sửa đổi exampleB(vì điều này cũng đã được sử dụng ở những nơi khác).



2
Giải pháp cho cái đó là sử dụng vprintf, và đó không phải là trường hợp ở đây.
Không có sẵn

Điều này có liên quan đến, nhưng chắc chắn không giống với, bản sao được đề xuất: Chuyển tiếp một lệnh gọi của một hàm biến thể trong C?
Jonathan Leffler

Câu trả lời:


127

Bạn không thể làm điều đó trực tiếp; bạn phải tạo một hàm có va_list:

#include <stdarg.h>

static void exampleV(int b, va_list args);

void exampleA(int a, int b, ...)    // Renamed for consistency
{
    va_list args;
    do_something(a);                // Use argument a somehow
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

void exampleB(int b, ...)
{
    va_list args;
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

static void exampleV(int b, va_list args)
{
    ...whatever you planned to have exampleB do...
    ...except it calls neither va_start nor va_end...
}

2
Nghi ngờ tôi phải làm một cái gì đó như thế này, vấn đề là các chức năng ví dụ cơ bản là một wrapper cho vsprintf và không có nhiều khác: /
Không Khả

@Xeross: Lưu ý rằng điều này không thay đổi đặc điểm kỹ thuật bên ngoài của những gì exampleB làm - nó chỉ thay đổi việc triển khai bên trong. Tôi không chắc vấn đề là gì.
Jonathan Leffler

Tham số đầu tiên là bắt buộc hay tất cả các tham số có thể khác nhau?
Qwerty

1
@Qwerty: Cú pháp yêu cầu một đối số đầu tiên được đặt tên cho một hàm có danh sách đối số biến có , ...trong chữ ký. Nếu bạn đã chuyển đổi các đối số biến thành a va_list, bạn có thể chuyển va_listcho một hàm khác chỉ nhận a va_list, nhưng hàm đó (hoặc một hàm mà nó gọi) phải có một số cách để biết có gì trong va_list.
Jonathan Leffler

46

Có thể ném một tảng đá xuống ao ở đây, nhưng nó có vẻ hoạt động khá ổn với các mẫu đa dạng C ++ 11:

#include <stdio.h>

template<typename... Args> void test(const char * f, Args... args) {
  printf(f, args...);
}

int main()
{
  int a = 2;
  test("%s\n", "test");
  test("%s %d %d %p\n", "second test", 2, a, &a);
}

Ít nhất, nó hoạt động với g++.


Tôi bối rối - đây có phải là cách tiếp cận hợp pháp bằng cách sử dụng C ++> = 11 không?
Duncan Jones

@DuncanJones Có, gói được mở rộng bởiargs...
Swordfish

15

bạn nên tạo các phiên bản của các hàm này lấy va_list và chuyển chúng. Hãy xem vprintfnhư một ví dụ:

int vprintf ( const char * format, va_list arg );

5

Tôi cũng muốn kết thúc printf và tìm thấy một câu trả lời hữu ích ở đây:

Cách truyền số đối số biến cho printf / sprintf

Tôi hoàn toàn không quan tâm đến hiệu suất (tôi chắc chắn rằng đoạn mã này có thể được cải thiện theo một số cách, hãy làm như vậy :)), điều này chỉ dành cho gỡ lỗi chung nên tôi đã làm điều này:

//Helper function
std::string osprintf(const char *fmt, ...)
{
    va_list args;
    char buf[1000];
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args );
    va_end(args);
    return buf;
}

mà sau đó tôi có thể sử dụng như thế này

Point2d p;

cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";

Các dòng lệnh c ++ đẹp ở một số khía cạnh, nhưng thực tế nó trở nên kinh khủng nếu bạn muốn in thứ gì đó như thế này với một số chuỗi nhỏ như dấu ngoặc đơn, dấu hai chấm và dấu phẩy được chèn giữa các số.


2

Ngẫu nhiên, nhiều triển khai C có một biến thể v? Printf bên trong mà IMHO lẽ ra phải là một phần của tiêu chuẩn C. Các chi tiết chính xác khác nhau, nhưng một triển khai điển hình sẽ chấp nhận một cấu trúc chứa một con trỏ hàm xuất ký tự và thông tin cho biết điều gì sẽ xảy ra. Điều này cho phép printf, sprintf và fprintf đều sử dụng cùng một cơ chế 'lõi'. Ví dụ: vsprintf có thể giống như sau:

void s_out (PRINTF_INFO * p_inf, char ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> kết quả ++;
}

int vsprintf (char * dest, const char * fmt, va_list args)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf (& p_inf, fmt, args);
}

Sau đó, hàm core_printf gọi p_inf-> func cho mỗi ký tự được xuất ra; sau đó hàm đầu ra có thể gửi các ký tự đến bảng điều khiển, một tệp, một chuỗi hoặc một cái gì đó khác. Nếu việc triển khai của một người làm lộ ra hàm core_printf (và bất kỳ cơ chế thiết lập nào mà nó sử dụng) thì người ta có thể mở rộng nó với đủ loại biến thể.


1

Một cách khả thi là sử dụng #define:

#define exampleB(int b, ...)  example(0, b, __VA_ARGS__)

0

Dựa trên nhận xét mà bạn đang gói vsprintfvà điều này được gắn thẻ là C ++, tôi khuyên bạn không nên cố gắng làm điều này, nhưng hãy thay đổi giao diện của bạn để sử dụng C ++ iostreams thay thế. Chúng có những ưu điểm vượt trội so với printdòng chức năng, chẳng hạn như an toàn về kiểu chữ và có thể in các mặt hàng printfkhông thể xử lý. Một số công việc làm lại bây giờ có thể tiết kiệm đáng kể cơn đau trong tương lai.


Bạn đang đề cập đến những lợi thế nào?
cjcurrie

@cjcurrie: ưu điểm là an toàn kiểu, ngay cả với kiểu do người dùng xác định. Tất nhiên, các hàm C không thể xử lý các kiểu do người dùng xác định.
Jonathan Leffler

-1

Sử dụng tiêu chuẩn C ++ 0x mới, bạn có thể hoàn thành việc này bằng cách sử dụng các mẫu đa dạng hoặc thậm chí chuyển đổi mã cũ đó sang cú pháp mẫu mới mà không vi phạm bất kỳ điều gì.


tiếc là điều này không thể thực hiện trong tất cả các trường - chỉ cần thử sử dụng lambdas
serup

-1

Đây là cách duy nhất để làm điều đó .. và cũng là cách tốt nhất để làm điều đó ..

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.