Оператор возврата не выполняется в c - PullRequest
0 голосов
/ 03 января 2019

Итак, у меня любопытный случай, и я не могу понять, что я сделал не так.Вот сценарий:

Я написал функцию-создатель, которая должна возвращать указатель на функцию.Чтобы заполнить структуру данными, я читаю в текстовом файле.В зависимости от того, какой текстовый файл я использую в качестве входных данных, ошибка либо возникает, либо не возникает.(Ошибка возникает для текстового файла с ~ 4000 строк, а не для файла с ~ 200, если это имеет значение).Странно то, что код выполняется вплоть до оператора return.Но потом не возвращается, а просто зависает.Нет ошибок, нет предупреждений компилятора (компилятор Intel).Интересно, сталкивался ли кто-нибудь с чем-то подобным или имел представление о том, что может быть не так.

Приведенный ниже код упрощен для иллюстрации проблемы.Фактический код несколько сложнее, так как я использую подход Шрайнера для игры с объектами в C.

struct Somestruct {
    int A;
    int B;
    int C;
}

static void *Somestruct_ctor(void *_self) {
    struct Somestruct *self = _self;
    fillDataFromFile(self);
    printf("This line gets executed.\n");
    return self; // <- this one doesn't
}

int main(int argc, char *argv[]) {
    void *myObject;

    myObject = Somestruct_ctor(myObject);
    printf("The code does NOT get until here\n");
    return 0;
}

1 Ответ

0 голосов
/ 03 января 2019

void * myObject; неинициализирован и не указывает на допустимое хранилище. Чтение его значения (для передачи его в качестве аргумента по значению Somestruct_ctor(myObject)) является неопределенным поведением.

Тот факт, что ваш код не всегда дает сбой, говорит нам о том, что в коде генерации ICC он указывает где-то действительный, возможно, где-то в стеке. С большим файлом мы, вероятно, получаем переполнение буфера, которое перезаписывает локальную переменную и / или адрес возврата и заканчивается бесконечным циклом. Удивительно, что это все равно не сработало, поскольку это произошло случайно. (В ассемблере x86-64 из ICC с отключенной оптимизацией он просто загружает некоторую неинициализированную память стека как аргумент для Somestruct_ctor .)

Или, возможно, указатель на структуру данных stdio, оставшуюся от init stdio до main. Возможно, наличие fillDataFromFile каракули по всем данным, на которые указывает FILE *stdout (например), оставило их в «заблокированном» состоянии, поэтому ваш единственный поток застрял в ожидании чего-то еще, чтобы разблокировать мьютекс. Если вы знаете asm, может быть интересно сделать один шаг по бесконечному циклу или «тупику» внутри printf и посмотреть, что именно произошло.


Если вы компилируете с gcc -O3, компилятор обнуляет регистр как аргумент для fillDataFromFile (после встраивания Somestruct_ctor), поэтому он передает указатель NULL. Это может произойти сбой всегда, если функция разыменовывает указатель.

clang решает оставить rdi (первый регистр передачи аргументов в x86-64 System V, вызывающей conventino) неинициализированным, поэтому он все еще остается argc, когда main вызывает fillDataFromFile. Это также может привести к краху.


Вы забыли включить предупреждения компилятора.

Все основные компиляторы x86 (gcc, clang, MSVC, ICC) имеют предупреждения об этом, но они не включены по умолчанию во всех компиляторах (только в MSVC). Возможно, потому что могут быть случаи, когда компилятор не уверен в том, что var неинициализирован, если есть какие-то условные вещи. В этом случае он на 100% уверен, что он определенно используется неинициализированным, но если инициализация и использование были в разных блоках if(), компилятор не сможет доказать, что использование произошло только в случае инициализации.

С clang и gcc вы обычно должны использовать -Wall и отключить все предупреждения.

С ICC -diag-enable:warn ближе к gcc -Wall. (-Wall ICC не включает это очень важное предупреждение. Не думайте, что вы включили все важные предупреждения с помощью icc -Wall.)

 # from icc -diag-enable:warn on your code
<source>(21): warning #592: variable "myObject" is used before its value is set
    myObject = Somestruct_ctor(myObject);
                               ^

как включить предупреждения icc / icpc? имеет некоторую информацию. Таким образом, -Wall ICC очень минималистичен по сравнению с GCC. Так что, возможно, -Wall -Wextra было бы полезно с icc. Он рекомендует -w2 или -w3 в качестве потенциально полезных уровней предупреждения.


Clang обычно имеет самые хорошие предупреждения, в этом случае:

<source>:21:30: warning: variable 'myObject' is uninitialized when used here [-Wuninitialized]
  myObject = Somestruct_ctor(myObject);
                             ^~~~~~~~
<source>:19:18: note: initialize the variable 'myObject' to silence this warning
  void * myObject;
                 ^
                  = NULL
1 warning generated.

Я получил вышеуказанные результаты, скомпилировав исходный код в проводнике компилятора Godbolt (после исправления синтаксических ошибок: пропущенная точка с запятой после структуры и использование заглавной буквы в ключевом слове Struct). -xc говорит компиляторам C ++ на Godbolt скомпилировать как C.

Оказывается, вам не нужно включать оптимизацию для icc и gcc, чтобы заметить эту ошибку. Некоторые предупреждения работают только с включенной оптимизацией, когда компилятор больше анализирует логику программы и может заметить больше, но они отслеживают неинициализированные даже при -O0.


код конструктора, который будет иметь больше смысла:

// C
int main(void){
  struct Somestruct myObject;     // automatic storage for the object value
  Somestruct_ctor(&myObject);     // pass a pointer to that storage
}

Объект должен где-то жить. Мы можем получить место для него с помощью автоматического (локального), статического (static локального или глобального) или динамического хранилища (malloc).

Автоматическое хранение + вызов конструктора эквивалентно C ++, как это, если struct Somestruct имеет конструктор C ++ по умолчанию, объявленный в определении структуры / класса.

// C++
int main(void){
  Somestruct myObject;     // calls the default constructor, if there is one
  // destructor will be called at some point when myObject goes out of scope
}
...